diff --git a/frontend/src/app/pages/AgentChat/ChatInput.tsx b/frontend/src/app/pages/AgentChat/ChatInput.tsx index f77bdd58..428bd878 100644 --- a/frontend/src/app/pages/AgentChat/ChatInput.tsx +++ b/frontend/src/app/pages/AgentChat/ChatInput.tsx @@ -76,7 +76,6 @@ const ChatInput = forwardRef(({ onSend, disabled, mode, prefilledRef.current = prefillPrompt; editor.style.opacity = '0.5'; editor.style.transition = 'opacity 0.15s'; - editor.focus(); const solidify = () => { editor.style.opacity = ''; editor.removeEventListener('keydown', solidify); diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx index d89ae699..18ac301f 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx @@ -73,7 +73,7 @@ interface DashboardCanvasProps { onNewAgent: () => void; onToolbarCancel: () => void; onToolbarSend: (...args: any[]) => void; - onStarterPrefill: (prompt: string) => void; + onStarter: (action: 'hover' | 'leave' | 'commit', prompt?: string) => void; toolbarPrefill?: string; onAddView: (outputId: string) => void; onHistoryResume: (sessionId: string) => void; @@ -132,7 +132,7 @@ const DashboardCanvas: React.FC = ({ onNewAgent, onToolbarCancel, onToolbarSend, - onStarterPrefill, + onStarter, toolbarPrefill, onAddView, onHistoryResume, @@ -219,7 +219,7 @@ const DashboardCanvas: React.FC = ({ /> {sessionList.length === 0 && Object.keys(viewCards).length === 0 && Object.keys(browserCards).length === 0 ? ( - + ) : (
void; - // Open the composer with the prompt typed in (unsent) so the user hits send. - onPrefill?: (prompt: string) => void; -}> = ({ c, onLaunch, onPrefill }) => { + // hover = preview-open the composer (translucent), leave = close it, + // commit = lock it open so the user can move to send. + onStarter?: (action: 'hover' | 'leave' | 'commit', prompt?: string) => void; +}> = ({ c, onLaunch, onStarter }) => { // The host hides Dashboard with visibility:hidden (not display:none), which keeps // CSS animations ticking; gate on active so the shimmer only burns while watched. const active = useDashboardActive(); @@ -81,17 +82,17 @@ const DashboardEmptyState: React.FC<{ // model connected); otherwise fall back to the plain hint. const showChips = !!onLaunch && canRun; + const isAppBuilder = currentCategory?.target === 'app-builder'; + + // Click commits the query into the composer (locks it open); the user then sends. const launch = (prompt: string) => { if (launching) return; - // Build prompts open the App Builder (live preview) with the prompt auto-sent. - if (currentCategory?.target === 'app-builder') { + if (isAppBuilder) { navigate(`/apps/new?prompt=${encodeURIComponent(prompt)}`); return; } - // Open the composer with the prompt typed in, unsent: the user sees the chat - // open with their message ready and clicks send. Falls back to direct launch. - if (onPrefill) { - onPrefill(prompt); + if (onStarter) { + onStarter('commit', prompt); return; } if (!onLaunch) return; @@ -201,12 +202,16 @@ const DashboardEmptyState: React.FC<{ > back - + { if (!isAppBuilder) onStarter?.('leave'); }} + sx={{ display: 'flex', flexDirection: 'column', gap: 0.9, width: '100%', maxWidth: 480 }} + > {currentPrompts.map((prompt) => ( launch(prompt)} + onMouseEnter={() => { if (!isAppBuilder) onStarter?.('hover', prompt); }} disabled={launching} sx={{ textAlign: 'left', diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts index 2a21c465..f0080a00 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts @@ -159,16 +159,28 @@ export function useDashboardController(dashboardId: string, isActive: boolean) { setSearchPaletteOpen, }); - // Starter-prompt prefill: opens the composer with the prompt typed in (unsent), - // so the user reviews and hits send. Bump a nonce so re-clicking the same prompt - // (after editing/clearing) still re-seeds. Cleared when the composer closes. + // Starter-prompt preview: HOVER opens the composer with the prompt typed in + // (translucent, unsent); leaving closes it again; CLICK locks it open so the + // user can move to the send button without it vanishing. Then they hit send. const [toolbarPrefill, setToolbarPrefill] = useState(undefined); - const handleStarterPrefill = useCallback((prompt: string) => { - setToolbarPrefill(prompt); - setToolbarOpen(true); + const starterLockedRef = useRef(false); + const handleStarter = useCallback((action: 'hover' | 'leave' | 'commit', prompt?: string) => { + if (action === 'hover' && prompt) { + setToolbarPrefill(prompt); + setToolbarOpen(true); + } else if (action === 'commit' && prompt) { + starterLockedRef.current = true; + setToolbarPrefill(prompt); + setToolbarOpen(true); + } else if (action === 'leave' && !starterLockedRef.current) { + setToolbarOpen(false); + } }, [setToolbarOpen]); useEffect(() => { - if (!toolbarOpen && toolbarPrefill) setToolbarPrefill(undefined); + if (!toolbarOpen) { + starterLockedRef.current = false; + if (toolbarPrefill) setToolbarPrefill(undefined); + } }, [toolbarOpen, toolbarPrefill]); useDashboardClipboard({ @@ -266,7 +278,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) { neighborDirections, toolbarOpen, searchPaletteOpen, newAgentBounce, toolbarRef, spawnOriginsRef, revealSpawnedRef, measuredHeightsRef, getCanvasState, toolbarPrefill, - onStarterPrefill: handleStarterPrefill, + onStarter: handleStarter, onViewportMouseDown: handleViewportMouseDown, onViewportMouseMove: handleViewportMouseMove, onViewportMouseUp: handleViewportMouseUp,