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

@@ -0,0 +1,18 @@
---
"@dustinbyrne/kb": patch
---
Fix text not being passed into Planning Mode from quick entry
Fixed a stale closure bug in PlanningModeModal where the auto-start useEffect
referenced handleStartPlanning before it was declared. The fix:
1. Moved handleStartPlanning definition before the auto-start useEffect
2. Removed the redundant handleStartPlanningWithPlan callback
3. Modified handleStartPlanning to accept an optional planOverride parameter
4. Fixed the onClick handler to wrap handleStartPlanning in an arrow function
5. Moved hasAutoStartedRef assignment inside setTimeout to prevent early
triggering that blocked subsequent effect runs
This ensures text entered in QuickEntryBox or InlineCreateCard is properly
passed to the Planning Mode modal when the Plan button is clicked.

View File

@@ -53,6 +53,60 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, tasks, initi
const streamConnectionRef = useRef<{ close: () => void; isConnected: () => boolean } | null>(null); const streamConnectionRef = useRef<{ close: () => void; isConnected: () => boolean } | null>(null);
const currentSessionIdRef = useRef<string | 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 // Focus textarea when opening
useEffect(() => { useEffect(() => {
if (isOpen && view.type === "initial") { if (isOpen && view.type === "initial") {
@@ -64,14 +118,15 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, tasks, initi
useEffect(() => { useEffect(() => {
if (isOpen && initialPlanProp && !hasAutoStartedRef.current && view.type === "initial") { if (isOpen && initialPlanProp && !hasAutoStartedRef.current && view.type === "initial") {
setInitialPlan(initialPlanProp); setInitialPlan(initialPlanProp);
hasAutoStartedRef.current = true;
// Use a small timeout to allow state update to propagate before starting // Use a small timeout to allow state update to propagate before starting
const timer = setTimeout(() => { const timer = setTimeout(() => {
handleStartPlanningWithPlan(initialPlanProp); // Only mark as auto-started when we actually start planning
hasAutoStartedRef.current = true;
handleStartPlanning(initialPlanProp);
}, 0); }, 0);
return () => clearTimeout(timer); return () => clearTimeout(timer);
} }
}, [isOpen, initialPlanProp, view.type]); }, [isOpen, initialPlanProp, view.type, handleStartPlanning]);
// Reset hasAutoStarted when modal closes // Reset hasAutoStarted when modal closes
useEffect(() => { useEffect(() => {
@@ -125,111 +180,6 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, tasks, initi
return () => document.removeEventListener("keydown", handleKeyDown); return () => document.removeEventListener("keydown", handleKeyDown);
}, [isOpen, view]); }, [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( const handleSubmitResponse = useCallback(
async (responses: QuestionResponse) => { async (responses: QuestionResponse) => {
if (view.type !== "question") return; if (view.type !== "question") return;
@@ -388,7 +338,7 @@ export function PlanningModeModal({ isOpen, onClose, onTaskCreated, tasks, initi
<div className="planning-view-footer"> <div className="planning-view-footer">
<button <button
className="btn btn-primary planning-start-btn" className="btn btn-primary planning-start-btn"
onClick={handleStartPlanning} onClick={() => handleStartPlanning()}
disabled={!initialPlan.trim()} disabled={!initialPlan.trim()}
> >
<Lightbulb size={16} style={{ marginRight: "8px" }} /> <Lightbulb size={16} style={{ marginRight: "8px" }} />