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:
18
.changeset/fix-planning-mode-text-passing.md
Normal file
18
.changeset/fix-planning-mode-text-passing.md
Normal 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.
|
||||||
@@ -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" }} />
|
||||||
|
|||||||
Reference in New Issue
Block a user