diff options
| author | soryu <soryu@soryu.co> | 2026-02-13 20:35:22 +0000 |
|---|---|---|
| committer | soryu <soryu@soryu.co> | 2026-02-13 20:35:22 +0000 |
| commit | ad5af0f7677c73fc159a3036b9479d1d847adf97 (patch) | |
| tree | 718633b10a12d1c36f51a903a1f4149c5c6677f7 | |
| parent | c19da4baf036a395c50dfbd19f6a1a91a69229a7 (diff) | |
| download | soryu-ad5af0f7677c73fc159a3036b9479d1d847adf97.tar.gz soryu-ad5af0f7677c73fc159a3036b9479d1d847adf97.zip | |
Directive page improvements
| -rw-r--r-- | makima/frontend/src/components/directives/DirectiveDetail.tsx | 12 | ||||
| -rw-r--r-- | makima/frontend/src/hooks/useDirectives.ts | 23 | ||||
| -rw-r--r-- | makima/frontend/src/hooks/useMultiTaskSubscription.ts | 5 | ||||
| -rw-r--r-- | makima/src/db/repository.rs | 17 |
4 files changed, 47 insertions, 10 deletions
diff --git a/makima/frontend/src/components/directives/DirectiveDetail.tsx b/makima/frontend/src/components/directives/DirectiveDetail.tsx index f9e7eed..b73463d 100644 --- a/makima/frontend/src/components/directives/DirectiveDetail.tsx +++ b/makima/frontend/src/components/directives/DirectiveDetail.tsx @@ -54,6 +54,16 @@ export function DirectiveDetail({ const hasTerminalTasks = directive.steps.some((s) => s.taskId && terminalStatuses.has(s.status)); // Build task map from directive steps and orchestrator + // Derive a stable key from the actual task IDs to avoid recreating the map on every poll + const taskMapKey = useMemo(() => { + const parts: string[] = []; + if (directive.orchestratorTaskId) parts.push(`o:${directive.orchestratorTaskId}`); + for (const step of directive.steps) { + if (step.taskId) parts.push(`${step.id}:${step.taskId}`); + } + return parts.join(","); + }, [directive.orchestratorTaskId, directive.steps]); + const taskMap = useMemo(() => { const map = new Map<string, string>(); if (directive.orchestratorTaskId) { @@ -65,7 +75,7 @@ export function DirectiveDetail({ } } return map; - }, [directive.orchestratorTaskId, directive.steps]); + }, [taskMapKey]); // eslint-disable-line react-hooks/exhaustive-deps // Subscribe to all task outputs const { connected, entries, clearEntries } = useMultiTaskSubscription({ diff --git a/makima/frontend/src/hooks/useDirectives.ts b/makima/frontend/src/hooks/useDirectives.ts index e67733c..0453d14 100644 --- a/makima/frontend/src/hooks/useDirectives.ts +++ b/makima/frontend/src/hooks/useDirectives.ts @@ -63,6 +63,19 @@ export function useDirective(id: string | undefined) { const [loading, setLoading] = useState(true); const [error, setError] = useState<string | null>(null); + // Silently refresh without setting loading state (for polls) + const silentRefresh = useCallback(async () => { + if (!id) return; + try { + const d = await getDirective(id); + setDirective(d); + setError(null); + } catch (e) { + // Don't overwrite existing data on poll failure + } + }, [id]); + + // Full refresh with loading state (for initial load / explicit refresh) const refresh = useCallback(async () => { if (!id) return; try { @@ -77,9 +90,13 @@ export function useDirective(id: string | undefined) { } }, [id]); + // Reset state and fetch when ID changes useEffect(() => { + setDirective(null); + setError(null); + setLoading(true); refresh(); - }, [refresh]); + }, [id]); // eslint-disable-line react-hooks/exhaustive-deps // Auto-poll while directive is active, has an orchestrator task, or has a completion task useEffect(() => { @@ -90,9 +107,9 @@ export function useDirective(id: string | undefined) { directive.completionTaskId != null; if (!needsPolling) return; - const interval = setInterval(refresh, 5000); + const interval = setInterval(silentRefresh, 5000); return () => clearInterval(interval); - }, [directive?.status, directive?.orchestratorTaskId, directive?.completionTaskId, refresh]); + }, [directive?.status, directive?.orchestratorTaskId, directive?.completionTaskId, silentRefresh]); const update = useCallback(async (req: UpdateDirectiveRequest) => { if (!id) return; diff --git a/makima/frontend/src/hooks/useMultiTaskSubscription.ts b/makima/frontend/src/hooks/useMultiTaskSubscription.ts index 19d6dea..4303f1b 100644 --- a/makima/frontend/src/hooks/useMultiTaskSubscription.ts +++ b/makima/frontend/src/hooks/useMultiTaskSubscription.ts @@ -38,8 +38,9 @@ export function useMultiTaskSubscription(options: UseMultiTaskSubscriptionOption enabledRef.current = enabled; }, [enabled]); - // Derive task IDs from the map - const taskIds = useMemo(() => Array.from(taskMap.keys()), [taskMap]); + // Derive task IDs from the map, stabilized to avoid unnecessary effect triggers + const taskIdsKey = useMemo(() => Array.from(taskMap.keys()).sort().join(","), [taskMap]); + const taskIds = useMemo(() => Array.from(taskMap.keys()), [taskIdsKey]); // eslint-disable-line react-hooks/exhaustive-deps const subscribeToTask = useCallback((ws: WebSocket, taskId: string) => { if (ws.readyState === WebSocket.OPEN) { diff --git a/makima/src/db/repository.rs b/makima/src/db/repository.rs index 8923f97..e288eba 100644 --- a/makima/src/db/repository.rs +++ b/makima/src/db/repository.rs @@ -4993,11 +4993,20 @@ pub async fn list_directives_for_owner( d.id, d.owner_id, d.title, d.goal, d.status, d.repository_url, d.orchestrator_task_id, d.pr_url, d.completion_task_id, d.version, d.created_at, d.updated_at, - COALESCE((SELECT COUNT(*) FROM directive_steps WHERE directive_id = d.id), 0) as total_steps, - COALESCE((SELECT COUNT(*) FROM directive_steps WHERE directive_id = d.id AND status = 'completed'), 0) as completed_steps, - COALESCE((SELECT COUNT(*) FROM directive_steps WHERE directive_id = d.id AND status = 'running'), 0) as running_steps, - COALESCE((SELECT COUNT(*) FROM directive_steps WHERE directive_id = d.id AND status = 'failed'), 0) as failed_steps + COALESCE(s.total_steps, 0) as total_steps, + COALESCE(s.completed_steps, 0) as completed_steps, + COALESCE(s.running_steps, 0) as running_steps, + COALESCE(s.failed_steps, 0) as failed_steps FROM directives d + LEFT JOIN LATERAL ( + SELECT + COUNT(*) as total_steps, + COUNT(*) FILTER (WHERE status = 'completed') as completed_steps, + COUNT(*) FILTER (WHERE status = 'running') as running_steps, + COUNT(*) FILTER (WHERE status = 'failed') as failed_steps + FROM directive_steps + WHERE directive_id = d.id + ) s ON true WHERE d.owner_id = $1 ORDER BY d.created_at DESC "#, |
