fix(dashboard): plumb planning draft text + model selection end-to-end
The planning modal's draft sync only updated SQLite, never the in-memory session, so Start Planning ran the agent against whatever fragment was typed before the first 500ms debounce and silently dropped everything after. Drafts that survived a backend restart couldn't be started at all (lazy rebuild was missing for the start path), reopened drafts left the textarea empty, and the model override the user picked at create time was forgotten on reopen — Start Planning would silently use the project default instead. This change rehydrates initialPlan from SQLite in startExistingSession, flushes the latest text from the request body before reading it back, lazy-rebuilds the in-memory session from the persisted row when needed, and round-trips the model override through inputPayload so reopen restores it into modal state. Sidebar drafts now render a derived preview (from inputPayload, never persisted as title) so multiple drafts are distinguishable while editing, and summarizeDraftTitle fires on textarea blur and modal close — gated on status rather than title content so blur-then-edit still refreshes rather than locking to the first snapshot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7161,6 +7161,8 @@ export interface AiSessionSummary {
|
||||
type: "planning" | "subtask" | "mission_interview" | "milestone_interview" | "slice_interview";
|
||||
status: "draft" | "generating" | "awaiting_input" | "complete" | "error";
|
||||
title: string;
|
||||
/** Server-derived preview of the in-progress initialPlan; only set for draft planning sessions. */
|
||||
preview?: string;
|
||||
projectId: string | null;
|
||||
lockedByTab: string | null;
|
||||
updatedAt: string;
|
||||
@@ -7279,7 +7281,7 @@ export function pingSession(sessionId: string, projectId?: string): Promise<{ ok
|
||||
|
||||
export function updatePlanningSessionDraft(
|
||||
sessionId: string,
|
||||
draft: { title: string; initialPlan: string },
|
||||
draft: { initialPlan: string; modelProvider?: string; modelId?: string },
|
||||
projectId?: string,
|
||||
): Promise<{ ok: boolean }> {
|
||||
return api<{ ok: boolean }>(withProjectId(`/ai-sessions/${encodeURIComponent(sessionId)}/draft`, projectId), {
|
||||
@@ -7288,6 +7290,22 @@ export function updatePlanningSessionDraft(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask the server to (re)generate the sidebar title for a draft planning
|
||||
* session from its persisted initialPlan. Server-side is idempotent and
|
||||
* a no-op once the session has been started, so callers can fire-and-
|
||||
* forget on textarea blur and modal close.
|
||||
*/
|
||||
export function summarizePlanningDraftTitle(
|
||||
sessionId: string,
|
||||
projectId?: string,
|
||||
): Promise<{ title: string | null }> {
|
||||
return api<{ title: string | null }>(
|
||||
withProjectId(`/planning/${encodeURIComponent(sessionId)}/summarize-draft-title`, projectId),
|
||||
{ method: "POST" },
|
||||
);
|
||||
}
|
||||
|
||||
// ── Messages API ──────────────────────────────────────────────────────────
|
||||
|
||||
/** Response shape for GET /messages/inbox */
|
||||
|
||||
Reference in New Issue
Block a user