diff --git a/src/__tests__/unit/components/features/CompactTasksList.test.tsx b/src/__tests__/unit/components/features/CompactTasksList.test.tsx
index 9e30bc9af..19b6f3ad6 100644
--- a/src/__tests__/unit/components/features/CompactTasksList.test.tsx
+++ b/src/__tests__/unit/components/features/CompactTasksList.test.tsx
@@ -1235,6 +1235,74 @@ describe("CompactTasksList", () => {
const graph = screen.getByTestId("dependency-graph");
expect(graph.className).toContain("h-[380px]");
});
+
+ test("auto-opens graph when tasks transition from 0 to N (post-generation)", () => {
+ (useIsMobile as any).mockReturnValue(false);
+ const feature = createMockFeature([]);
+
+ const { rerender } = render(
+
+ );
+
+ // Initially no tasks → hasDependencies = false → collapsible not rendered
+ expect(screen.queryByTestId("collapsible")).not.toBeInTheDocument();
+
+ // Tasks arrive via Pusher / generation
+ const task1 = createMockTask({ id: "t1", dependsOnTaskIds: [] });
+ const task2 = createMockTask({ id: "t2", dependsOnTaskIds: ["t1"] });
+ const updatedFeature = createMockFeature([task1, task2]);
+
+ rerender(
+
+ );
+
+ expect(screen.getByTestId("collapsible")).toHaveAttribute("data-open", "true");
+ });
+
+ test("does not auto-open graph when adding a task to an already-populated list (N → N+1)", () => {
+ (useIsMobile as any).mockReturnValue(false);
+ // Start with 1 task — graphOpen initialises to false
+ const task1 = createMockTask({ id: "t1", dependsOnTaskIds: [] });
+ const feature = createMockFeature([task1]);
+
+ const { rerender } = render(
+
+ );
+
+ // Initially no dependencies on the 1 task → hasDependencies = false → collapsible not rendered
+ expect(screen.queryByTestId("collapsible")).not.toBeInTheDocument();
+
+ // A second task is added (1 → 2, not 0 → N)
+ const task2 = createMockTask({ id: "t2", dependsOnTaskIds: ["t1"] });
+ const updatedFeature = createMockFeature([task1, task2]);
+
+ rerender(
+
+ );
+
+ // Graph should remain closed — effect only fires on 0 → N transition
+ expect(screen.getByTestId("collapsible")).toHaveAttribute("data-open", "false");
+ });
});
describe("Pusher feature channel subscription", () => {
diff --git a/src/components/features/CompactTasksList/index.tsx b/src/components/features/CompactTasksList/index.tsx
index 295ce27b0..b159a44c0 100644
--- a/src/components/features/CompactTasksList/index.tsx
+++ b/src/components/features/CompactTasksList/index.tsx
@@ -1,6 +1,6 @@
"use client";
-import React, { useState, useMemo, useCallback, useEffect } from "react";
+import React, { useState, useMemo, useCallback, useEffect, useRef } from "react";
import { useRouter } from "next/navigation";
import { ChevronDown, ExternalLink, Play, Trash2, RefreshCw, Copy, Sparkles, CheckCircle } from "lucide-react";
import { Button } from "@/components/ui/button";
@@ -125,6 +125,14 @@ export function CompactTasksList({ featureId, feature, onUpdate, isGenerating }:
const [graphOpen, setGraphOpen] = useState(tasks.length > 1);
+ const prevTasksLengthRef = useRef(tasks.length);
+ useEffect(() => {
+ if (prevTasksLengthRef.current === 0 && tasks.length > 0) {
+ setGraphOpen(true);
+ }
+ prevTasksLengthRef.current = tasks.length;
+ }, [tasks.length]);
+
const hasDependencies = useMemo(
() => tasks.some((t) => (t.dependsOnTaskIds ?? []).length > 0),
[tasks]