fix(KB-263): fix text not being passed into Planning Mode from quick entry

- Fix stale closure bug in PlanningModeModal auto-start useEffect
- Move handleStartPlanning definition before the effect that references it
- Remove redundant handleStartPlanningWithPlan callback
- Add planOverride parameter to handleStartPlanning for proper prop passing
- Fix hasAutoStartedRef timing to prevent early triggering
This commit is contained in:
gsxdsm
2026-03-30 23:39:20 -07:00
parent cea55fa665
commit 2a222a2be6
2 changed files with 77 additions and 109 deletions

View File

@@ -53,6 +53,60 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, tasks, initi
const streamConnectionRef = useRef<{ close: () => void; isConnected: () => boolean } | null>(null);
const currentSessionIdRef = useRef<string | null>(null);
const handleStartPlanning = useCallback(async (planOverride?: string) => {
const plan = planOverride ?? initialPlan;
if (!plan.trim()) return;
setError(null);
setStreamingOutput("");
setView({ type: "loading" });
try {
// Use streaming mode for real-time AI thinking display
const { sessionId } = await startPlanningStreaming(plan.trim());
currentSessionIdRef.current = sessionId;
// Connect to SSE stream
const connection = connectPlanningStream(sessionId, {
onThinking: (data) => {
setStreamingOutput((prev) => prev + data);
},
onQuestion: (question) => {
setView({
type: "question",
session: { sessionId, currentQuestion: question, summary: null },
});
setStreamingOutput("");
},
onSummary: (summary) => {
setView({
type: "summary",
session: { sessionId, currentQuestion: null, summary },
summary,
});
setEditedSummary(summary);
setStreamingOutput("");
},
onError: (message) => {
setError(message);
setView({ type: "initial" });
setStreamingOutput("");
currentSessionIdRef.current = null;
},
onComplete: () => {
currentSessionIdRef.current = null;
},
});
streamConnectionRef.current = connection;
setResponseHistory([]);
} catch (err: any) {
setError(err.message || "Failed to start planning session");
setView({ type: "initial" });
currentSessionIdRef.current = null;
}
}, [initialPlan]);
// Focus textarea when opening
useEffect(() => {
if (isOpen && view.type === "initial") {
@@ -64,14 +118,15 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, tasks, initi
useEffect(() => {
if (isOpen && initialPlanProp && !hasAutoStartedRef.current && view.type === "initial") {
setInitialPlan(initialPlanProp);
hasAutoStartedRef.current = true;
// Use a small timeout to allow state update to propagate before starting
const timer = setTimeout(() => {
handleStartPlanningWithPlan(initialPlanProp);
// Only mark as auto-started when we actually start planning
hasAutoStartedRef.current = true;
handleStartPlanning(initialPlanProp);
}, 0);
return () => clearTimeout(timer);
}
}, [isOpen, initialPlanProp, view.type]);
}, [isOpen, initialPlanProp, view.type, handleStartPlanning]);
// Reset hasAutoStarted when modal closes
useEffect(() => {
@@ -125,111 +180,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, tasks, initi
return () => document.removeEventListener("keydown", handleKeyDown);
}, [isOpen, view]);
const handleStartPlanning = useCallback(async () => {
if (!initialPlan.trim()) return;
setError(null);
setStreamingOutput("");
setView({ type: "loading" });
try {
// Use streaming mode for real-time AI thinking display
const { sessionId } = await startPlanningStreaming(initialPlan.trim());
currentSessionIdRef.current = sessionId;
// Connect to SSE stream
const connection = connectPlanningStream(sessionId, {
onThinking: (data) => {
setStreamingOutput((prev) => prev + data);
},
onQuestion: (question) => {
setView({
type: "question",
session: { sessionId, currentQuestion: question, summary: null },
});
setStreamingOutput("");
},
onSummary: (summary) => {
setView({
type: "summary",
session: { sessionId, currentQuestion: null, summary },
summary,
});
setEditedSummary(summary);
setStreamingOutput("");
},
onError: (message) => {
setError(message);
setView({ type: "initial" });
setStreamingOutput("");
currentSessionIdRef.current = null;
},
onComplete: () => {
currentSessionIdRef.current = null;
},
});
streamConnectionRef.current = connection;
setResponseHistory([]);
} catch (err: any) {
setError(err.message || "Failed to start planning session");
setView({ type: "initial" });
currentSessionIdRef.current = null;
}
}, [initialPlan]);
// Helper for auto-start with a specific plan (from prop)
const handleStartPlanningWithPlan = useCallback(async (plan: string) => {
if (!plan.trim()) return;
setError(null);
setStreamingOutput("");
setView({ type: "loading" });
try {
const { sessionId } = await startPlanningStreaming(plan.trim());
currentSessionIdRef.current = sessionId;
const connection = connectPlanningStream(sessionId, {
onThinking: (data) => {
setStreamingOutput((prev) => prev + data);
},
onQuestion: (question) => {
setView({
type: "question",
session: { sessionId, currentQuestion: question, summary: null },
});
setStreamingOutput("");
},
onSummary: (summary) => {
setView({
type: "summary",
session: { sessionId, currentQuestion: null, summary },
summary,
});
setEditedSummary(summary);
setStreamingOutput("");
},
onError: (message) => {
setError(message);
setView({ type: "initial" });
setStreamingOutput("");
currentSessionIdRef.current = null;
},
onComplete: () => {
currentSessionIdRef.current = null;
},
});
streamConnectionRef.current = connection;
setResponseHistory([]);
} catch (err: any) {
setError(err.message || "Failed to start planning session");
setView({ type: "initial" });
currentSessionIdRef.current = null;
}
}, []);
const handleSubmitResponse = useCallback(
async (responses: QuestionResponse) => {
if (view.type !== "question") return;
@@ -388,7 +338,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, tasks, initi
<div className="planning-view-footer">
<button
className="btn btn-primary planning-start-btn"
onClick={handleStartPlanning}
onClick={() => handleStartPlanning()}
disabled={!initialPlan.trim()}
>
<Lightbulb size={16} style={{ marginRight: "8px" }} />