diff --git a/apps/code/src/renderer/components/MainLayout.tsx b/apps/code/src/renderer/components/MainLayout.tsx
index b0867db06..dd54727be 100644
--- a/apps/code/src/renderer/components/MainLayout.tsx
+++ b/apps/code/src/renderer/components/MainLayout.tsx
@@ -45,6 +45,8 @@ import { GlobalEventHandlers } from "./GlobalEventHandlers";
const log = logger.scope("main-layout");
+const TOURS_ENABLED = false;
+
export function MainLayout() {
const {
view,
@@ -150,7 +152,7 @@ export function MainLayout() {
const settingsOpen = useSettingsDialogStore((s) => s.isOpen);
useEffect(() => {
- if (isFirstTaskTourDone || settingsOpen) return;
+ if (!TOURS_ENABLED || isFirstTaskTourDone || settingsOpen) return;
const timer = setTimeout(() => startTour(createFirstTaskTour.id), 600);
return () => clearTimeout(timer);
}, [isFirstTaskTourDone, settingsOpen, startTour]);
diff --git a/apps/code/src/renderer/features/task-detail/components/SuggestedTaskChips.tsx b/apps/code/src/renderer/features/task-detail/components/SuggestedTaskChips.tsx
new file mode 100644
index 000000000..8e2920709
--- /dev/null
+++ b/apps/code/src/renderer/features/task-detail/components/SuggestedTaskChips.tsx
@@ -0,0 +1,73 @@
+import { useSetupStore } from "@features/setup/stores/setupStore";
+import type { DiscoveredTask } from "@features/setup/types";
+import {
+ CATEGORY_CONFIG,
+ FALLBACK_CATEGORY_CONFIG,
+} from "@features/setup/utils/categoryConfig";
+import { Chip } from "@posthog/quill";
+import { Tooltip } from "@radix-ui/themes";
+import { useState } from "react";
+
+const COLLAPSED_LIMIT = 4;
+
+interface SuggestedTaskChipsProps {
+ onSelect: (task: DiscoveredTask) => void;
+}
+
+export function SuggestedTaskChips({ onSelect }: SuggestedTaskChipsProps) {
+ const discoveredTasks = useSetupStore((s) => s.discoveredTasks);
+ const [expanded, setExpanded] = useState(false);
+
+ if (discoveredTasks.length === 0) return null;
+
+ const visible =
+ expanded || discoveredTasks.length <= COLLAPSED_LIMIT
+ ? discoveredTasks
+ : discoveredTasks.slice(0, COLLAPSED_LIMIT);
+ const hiddenCount = discoveredTasks.length - visible.length;
+
+ return (
+
+ {visible.map((task) => (
+
+ ))}
+ {hiddenCount > 0 && (
+ setExpanded(true)}
+ className="cursor-pointer! whitespace-nowrap text-(--gray-11)"
+ >
+ +{hiddenCount} more
+
+ )}
+
+ );
+}
+
+function SuggestedTaskChip({
+ task,
+ onSelect,
+}: {
+ task: DiscoveredTask;
+ onSelect: (task: DiscoveredTask) => void;
+}) {
+ const config = CATEGORY_CONFIG[task.category] ?? FALLBACK_CATEGORY_CONFIG;
+ const TaskIcon = config.icon;
+
+ return (
+
+ onSelect(task)}
+ className="max-w-[220px] cursor-pointer! gap-1.5 whitespace-nowrap pl-2"
+ >
+
+ {task.title}
+
+
+ );
+}
diff --git a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx
index d0302697d..3ab1a5967 100644
--- a/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx
+++ b/apps/code/src/renderer/features/task-detail/components/TaskInput.tsx
@@ -23,6 +23,8 @@ import { UnifiedModelSelector } from "@features/sessions/components/UnifiedModel
import { getCurrentModeFromConfigOptions } from "@features/sessions/stores/sessionStore";
import type { AgentAdapter } from "@features/settings/stores/settingsStore";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
+import { useSetupStore } from "@features/setup/stores/setupStore";
+import type { DiscoveredTask } from "@features/setup/types";
import { useAutoFocusOnTyping } from "@hooks/useAutoFocusOnTyping";
import { useConnectivity } from "@hooks/useConnectivity";
import {
@@ -47,6 +49,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { usePreviewConfig } from "../hooks/usePreviewConfig";
import { useTaskCreation } from "../hooks/useTaskCreation";
import { CloudGithubMissingNotice } from "./CloudGithubMissingNotice";
+import { SuggestedTaskChips } from "./SuggestedTaskChips";
import { type WorkspaceMode, WorkspaceModeSelect } from "./WorkspaceModeSelect";
interface TaskInputProps {
@@ -539,6 +542,15 @@ export function TaskInput({
editorRef.current?.setContent(text);
editorRef.current?.focus();
}, []);
+ const handleSelectSuggestion = useCallback(
+ async (task: DiscoveredTask) => {
+ const ok = await handleSubmit({
+ segments: [{ type: "text", text: task.prompt ?? task.title }],
+ });
+ if (ok) useSetupStore.getState().removeDiscoveredTask(task.id);
+ },
+ [handleSubmit],
+ );
const hasPendingDraft = useCallback(
() => !(editorRef.current?.isEmpty() ?? true),
[],
@@ -825,6 +837,7 @@ export function TaskInput({
)}
+
diff --git a/apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts b/apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts
index 6169c42ce..52cf459a8 100644
--- a/apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts
+++ b/apps/code/src/renderer/features/task-detail/hooks/useTaskCreation.ts
@@ -4,6 +4,7 @@ import { useTaskInputHistoryStore } from "@features/message-editor/stores/taskIn
import type { EditorHandle } from "@features/message-editor/types";
import {
contentToXml,
+ type EditorContent,
extractFilePaths,
} from "@features/message-editor/utils/content";
import { useSettingsStore } from "@features/settings/stores/settingsStore";
@@ -48,7 +49,7 @@ interface UseTaskCreationOptions {
interface UseTaskCreationReturn {
isCreatingTask: boolean;
canSubmit: boolean;
- handleSubmit: () => void;
+ handleSubmit: (contentOverride?: EditorContent) => Promise;
}
function prepareTaskInput(
@@ -204,96 +205,114 @@ export function useTaskCreation({
!isCreatingTask &&
!editorIsEmpty;
- const handleSubmit = useCallback(async () => {
- const editor = editorRef.current;
- if (!canSubmit || !editor) return;
+ const handleSubmit = useCallback(
+ async (contentOverride?: EditorContent): Promise => {
+ const editor = editorRef.current;
+ const allowSubmit = contentOverride
+ ? isAuthenticated &&
+ isOnline &&
+ hasRequiredPath &&
+ !isCreatingTask &&
+ !!editor
+ : canSubmit && !!editor;
+ if (!allowSubmit || !editor) return false;
- setIsCreatingTask(true);
+ setIsCreatingTask(true);
- try {
- const content = editor.getContent();
-
- const plainText = editor.getText()?.trim();
- if (plainText) {
- useTaskInputHistoryStore.getState().addPrompt(plainText);
- }
+ try {
+ const content = contentOverride ?? editor.getContent();
- const input = prepareTaskInput(content, {
- selectedDirectory,
- selectedRepository,
- githubIntegrationId,
- githubUserIntegrationId,
- workspaceMode,
- branch,
- executionMode,
- adapter,
- model,
- reasoningLevel,
- environmentId,
- sandboxEnvironmentId,
- signalReportId,
- });
+ if (!contentOverride) {
+ const plainText = editor.getText()?.trim();
+ if (plainText) {
+ useTaskInputHistoryStore.getState().addPrompt(plainText);
+ }
+ }
- if (executionMode) {
- useSettingsStore.getState().setLastUsedInitialTaskMode(executionMode);
- }
+ const input = prepareTaskInput(content, {
+ selectedDirectory,
+ selectedRepository,
+ githubIntegrationId,
+ githubUserIntegrationId,
+ workspaceMode,
+ branch,
+ executionMode,
+ adapter,
+ model,
+ reasoningLevel,
+ environmentId,
+ sandboxEnvironmentId,
+ signalReportId,
+ });
- const taskService = get(RENDERER_TOKENS.TaskService);
- const result = await taskService.createTask(input, (output) => {
- invalidateTasks(output.task);
- if (signalReportId) {
- clearTaskInputReportAssociation();
- }
- if (onTaskCreated) {
- onTaskCreated(output.task);
- } else {
- navigateToTask(output.task);
+ if (executionMode) {
+ useSettingsStore.getState().setLastUsedInitialTaskMode(executionMode);
}
- useTourStore.getState().completeTour(createFirstTaskTour.id);
- editor.clear();
- });
-
- if (result.success) {
- void trackTaskCreated(input, selectedDirectory);
- }
- if (!result.success) {
- const title = getErrorTitle(result.failedStep);
- toast.error(title, { description: result.error });
- log.error("Task creation failed", {
- failedStep: result.failedStep,
- error: result.error,
+ const taskService = get(RENDERER_TOKENS.TaskService);
+ const result = await taskService.createTask(input, (output) => {
+ invalidateTasks(output.task);
+ if (signalReportId) {
+ clearTaskInputReportAssociation();
+ }
+ if (onTaskCreated) {
+ onTaskCreated(output.task);
+ } else {
+ navigateToTask(output.task);
+ }
+ useTourStore.getState().completeTour(createFirstTaskTour.id);
+ editor.clear();
});
+
+ if (result.success) {
+ void trackTaskCreated(input, selectedDirectory);
+ }
+
+ if (!result.success) {
+ const title = getErrorTitle(result.failedStep);
+ toast.error(title, { description: result.error });
+ log.error("Task creation failed", {
+ failedStep: result.failedStep,
+ error: result.error,
+ });
+ }
+ return result.success;
+ } catch (error) {
+ const description =
+ error instanceof Error ? error.message : "Unknown error";
+ toast.error("Failed to create task", { description });
+ log.error("Unexpected error during task creation", { error });
+ return false;
+ } finally {
+ setIsCreatingTask(false);
}
- } catch (error) {
- const description =
- error instanceof Error ? error.message : "Unknown error";
- toast.error("Failed to create task", { description });
- log.error("Unexpected error during task creation", { error });
- } finally {
- setIsCreatingTask(false);
- }
- }, [
- canSubmit,
- editorRef,
- selectedDirectory,
- selectedRepository,
- githubIntegrationId,
- githubUserIntegrationId,
- workspaceMode,
- branch,
- executionMode,
- adapter,
- model,
- reasoningLevel,
- environmentId,
- sandboxEnvironmentId,
- signalReportId,
- clearTaskInputReportAssociation,
- invalidateTasks,
- navigateToTask,
- onTaskCreated,
- ]);
+ },
+ [
+ canSubmit,
+ editorRef,
+ isAuthenticated,
+ isOnline,
+ hasRequiredPath,
+ isCreatingTask,
+ selectedDirectory,
+ selectedRepository,
+ githubIntegrationId,
+ githubUserIntegrationId,
+ workspaceMode,
+ branch,
+ executionMode,
+ adapter,
+ model,
+ reasoningLevel,
+ environmentId,
+ sandboxEnvironmentId,
+ signalReportId,
+ clearTaskInputReportAssociation,
+ invalidateTasks,
+ navigateToTask,
+ onTaskCreated,
+ ],
+ );
return {
isCreatingTask,