From 46b6078d0112aca2eff191b56ea3eb7518df1427 Mon Sep 17 00:00:00 2001 From: shivanathd Date: Mon, 5 Jan 2026 23:38:36 +0530 Subject: [PATCH 1/4] feat: Add project reset functionality Allow users to reset a project to its initial state without having to re-register it. This is useful when a project initialization fails or when users want to start fresh. Changes: - Add POST /api/projects/{name}/reset endpoint - Add ResetProjectModal component with confirmation dialog - Add useResetProject hook for React Query integration - Add Reset button in header (keyboard shortcut: R) - Disable reset while agent is running The reset clears features.db, assistant.db, and settings files while preserving the prompts directory with app_spec.txt and templates. --- server/routers/projects.py | 71 +++++++++++++++ ui/src/App.tsx | 37 +++++++- ui/src/components/ResetProjectModal.tsx | 113 ++++++++++++++++++++++++ ui/src/hooks/useProjects.ts | 14 +++ ui/src/lib/api.ts | 12 +++ 5 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 ui/src/components/ResetProjectModal.tsx diff --git a/server/routers/projects.py b/server/routers/projects.py index 2e190fba..60c59b36 100644 --- a/server/routers/projects.py +++ b/server/routers/projects.py @@ -259,6 +259,77 @@ async def delete_project(name: str, delete_files: bool = False): } +@router.post("/{name}/reset") +async def reset_project(name: str): + """ + Reset a project to its initial state. + + This clears all features, assistant chat history, and settings while + preserving the prompts directory. Use this to restart a project from scratch + without having to re-register it. + + Deletes: + - features.db (feature tracking database) + - assistant.db (assistant chat history) + - .claude_settings.json (agent settings) + - .claude_assistant_settings.json (assistant settings) + + Preserves: + - prompts/ directory (app_spec.txt, initializer_prompt.md, coding_prompt.md) + """ + _init_imports() + _, _, get_project_path, _, _ = _get_registry_functions() + + name = validate_project_name(name) + project_dir = get_project_path(name) + + if not project_dir: + raise HTTPException(status_code=404, detail=f"Project '{name}' not found") + + if not project_dir.exists(): + raise HTTPException(status_code=404, detail=f"Project directory not found") + + # Check if agent is running + lock_file = project_dir / ".agent.lock" + if lock_file.exists(): + raise HTTPException( + status_code=409, + detail="Cannot reset project while agent is running. Stop the agent first." + ) + + # Files to delete + files_to_delete = [ + "features.db", + "assistant.db", + ".claude_settings.json", + ".claude_assistant_settings.json", + ] + + deleted_files = [] + errors = [] + + for filename in files_to_delete: + filepath = project_dir / filename + if filepath.exists(): + try: + filepath.unlink() + deleted_files.append(filename) + except Exception as e: + errors.append(f"{filename}: {e}") + + if errors: + raise HTTPException( + status_code=500, + detail=f"Failed to delete some files: {'; '.join(errors)}" + ) + + return { + "success": True, + "message": f"Project '{name}' has been reset", + "deleted_files": deleted_files, + } + + @router.get("/{name}/prompts", response_model=ProjectPrompts) async def get_project_prompts(name: str): """Get the content of project prompt files.""" diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 96066b24..4125e405 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -16,7 +16,8 @@ import { DebugLogViewer } from './components/DebugLogViewer' import { AgentThought } from './components/AgentThought' import { AssistantFAB } from './components/AssistantFAB' import { AssistantPanel } from './components/AssistantPanel' -import { Plus, Loader2 } from 'lucide-react' +import { ResetProjectModal } from './components/ResetProjectModal' +import { Plus, Loader2, RotateCcw } from 'lucide-react' import type { Feature } from './lib/types' function App() { @@ -34,6 +35,7 @@ function App() { const [debugOpen, setDebugOpen] = useState(false) const [debugPanelHeight, setDebugPanelHeight] = useState(288) // Default height const [assistantOpen, setAssistantOpen] = useState(false) + const [showResetModal, setShowResetModal] = useState(false) const { data: projects, isLoading: projectsLoading } = useProjects() const { data: features } = useFeatures(selectedProject) @@ -93,9 +95,17 @@ function App() { setAssistantOpen(prev => !prev) } + // R : Reset project (when project selected and agent not running) + if ((e.key === 'r' || e.key === 'R') && selectedProject && wsState.agentStatus !== 'running') { + e.preventDefault() + setShowResetModal(true) + } + // Escape : Close modals if (e.key === 'Escape') { - if (assistantOpen) { + if (showResetModal) { + setShowResetModal(false) + } else if (assistantOpen) { setAssistantOpen(false) } else if (showAddFeature) { setShowAddFeature(false) @@ -109,7 +119,7 @@ function App() { window.addEventListener('keydown', handleKeyDown) return () => window.removeEventListener('keydown', handleKeyDown) - }, [selectedProject, showAddFeature, selectedFeature, debugOpen, assistantOpen]) + }, [selectedProject, showAddFeature, selectedFeature, debugOpen, assistantOpen, showResetModal, wsState.agentStatus]) // Combine WebSocket progress with feature data const progress = wsState.progress.total > 0 ? wsState.progress : { @@ -160,6 +170,19 @@ function App() { + + )} + {/* Reset Project Modal */} + {showResetModal && selectedProject && ( + setShowResetModal(false)} + /> + )} + {/* Debug Log Viewer - fixed to bottom */} {selectedProject && ( void + onReset?: () => void +} + +export function ResetProjectModal({ projectName, onClose, onReset }: ResetProjectModalProps) { + const [error, setError] = useState(null) + const resetProject = useResetProject() + + const handleReset = async () => { + setError(null) + try { + await resetProject.mutateAsync(projectName) + onReset?.() + onClose() + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to reset project') + } + } + + return ( +
+
e.stopPropagation()} + > + {/* Header */} +
+

+ + Reset Project +

+ +
+ + {/* Content */} +
+ {/* Error Message */} + {error && ( +
+ + {error} + +
+ )} + +

+ Are you sure you want to reset {projectName}? +

+ +
+

This will delete:

+
    +
  • All features and their progress
  • +
  • Assistant chat history
  • +
  • Agent settings
  • +
+
+ +
+

This will preserve:

+
    +
  • App spec (prompts/app_spec.txt)
  • +
  • Prompt templates
  • +
  • Project registration
  • +
+
+ + {/* Actions */} +
+ + +
+
+
+
+ ) +} diff --git a/ui/src/hooks/useProjects.ts b/ui/src/hooks/useProjects.ts index 0cb61fa5..b25347e3 100644 --- a/ui/src/hooks/useProjects.ts +++ b/ui/src/hooks/useProjects.ts @@ -48,6 +48,20 @@ export function useDeleteProject() { }) } +export function useResetProject() { + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: (name: string) => api.resetProject(name), + onSuccess: (_, name) => { + // Invalidate both projects and features queries + queryClient.invalidateQueries({ queryKey: ['projects'] }) + queryClient.invalidateQueries({ queryKey: ['features', name] }) + queryClient.invalidateQueries({ queryKey: ['project', name] }) + }, + }) +} + // ============================================================================ // Features // ============================================================================ diff --git a/ui/src/lib/api.ts b/ui/src/lib/api.ts index bfee6cc9..feab62f3 100644 --- a/ui/src/lib/api.ts +++ b/ui/src/lib/api.ts @@ -66,6 +66,18 @@ export async function deleteProject(name: string): Promise { }) } +export interface ResetProjectResponse { + success: boolean + message: string + deleted_files: string[] +} + +export async function resetProject(name: string): Promise { + return fetchJSON(`/projects/${encodeURIComponent(name)}/reset`, { + method: 'POST', + }) +} + export async function getProjectPrompts(name: string): Promise { return fetchJSON(`/projects/${encodeURIComponent(name)}/prompts`) } From 1693dbe7d503be6daec3cb0caab8bb2d9e6bd4c1 Mon Sep 17 00:00:00 2001 From: shivanathd Date: Mon, 5 Jan 2026 23:42:30 +0530 Subject: [PATCH 2/4] Add full reset option to reset feature - Backend: Add full_reset query parameter to POST /api/projects/{name}/reset - When full_reset=true, also deletes prompts/ directory - Allows starting completely fresh with setup wizard - Frontend: Enhanced ResetProjectModal with two options: - Quick Reset: Clear features/history, keep prompts - Full Reset: Delete everything including prompts - Removed R keyboard shortcut for reset modal - Updated API client and hooks to support fullReset parameter --- server/routers/projects.py | 37 +++++++++--- ui/src/App.tsx | 11 +--- ui/src/components/ResetProjectModal.tsx | 80 ++++++++++++++++++++++--- ui/src/hooks/useProjects.ts | 5 +- ui/src/lib/api.ts | 6 +- ui/tsconfig.tsbuildinfo | 2 +- 6 files changed, 110 insertions(+), 31 deletions(-) diff --git a/server/routers/projects.py b/server/routers/projects.py index 60c59b36..b7f01fc5 100644 --- a/server/routers/projects.py +++ b/server/routers/projects.py @@ -259,23 +259,34 @@ async def delete_project(name: str, delete_files: bool = False): } +class ResetOptions: + """Options for project reset.""" + delete_prompts: bool = False + + @router.post("/{name}/reset") -async def reset_project(name: str): +async def reset_project(name: str, full_reset: bool = False): """ Reset a project to its initial state. - This clears all features, assistant chat history, and settings while - preserving the prompts directory. Use this to restart a project from scratch - without having to re-register it. + This clears all features, assistant chat history, and settings. + Use this to restart a project from scratch without having to re-register it. - Deletes: + Args: + name: Project name to reset + full_reset: If True, also deletes prompts directory for complete fresh start + + Always Deletes: - features.db (feature tracking database) - assistant.db (assistant chat history) - .claude_settings.json (agent settings) - .claude_assistant_settings.json (assistant settings) - Preserves: + When full_reset=True, Also Deletes: - prompts/ directory (app_spec.txt, initializer_prompt.md, coding_prompt.md) + + Preserves: + - Project registration in registry """ _init_imports() _, _, get_project_path, _, _ = _get_registry_functions() @@ -317,16 +328,28 @@ async def reset_project(name: str): except Exception as e: errors.append(f"{filename}: {e}") + # If full reset, also delete prompts directory + if full_reset: + prompts_dir = project_dir / "prompts" + if prompts_dir.exists(): + try: + shutil.rmtree(prompts_dir) + deleted_files.append("prompts/") + except Exception as e: + errors.append(f"prompts/: {e}") + if errors: raise HTTPException( status_code=500, detail=f"Failed to delete some files: {'; '.join(errors)}" ) + reset_type = "fully reset" if full_reset else "reset" return { "success": True, - "message": f"Project '{name}' has been reset", + "message": f"Project '{name}' has been {reset_type}", "deleted_files": deleted_files, + "full_reset": full_reset, } diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 4125e405..cb6ef093 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -95,12 +95,6 @@ function App() { setAssistantOpen(prev => !prev) } - // R : Reset project (when project selected and agent not running) - if ((e.key === 'r' || e.key === 'R') && selectedProject && wsState.agentStatus !== 'running') { - e.preventDefault() - setShowResetModal(true) - } - // Escape : Close modals if (e.key === 'Escape') { if (showResetModal) { @@ -173,14 +167,11 @@ function App() { (null) + const [fullReset, setFullReset] = useState(false) const resetProject = useResetProject() const handleReset = async () => { setError(null) try { - await resetProject.mutateAsync(projectName) + await resetProject.mutateAsync({ name: projectName, fullReset }) onReset?.() onClose() } catch (err) { @@ -26,7 +27,7 @@ export function ResetProjectModal({ projectName, onClose, onReset }: ResetProjec return (
e.stopPropagation()} > {/* Header */} @@ -61,24 +62,81 @@ export function ResetProjectModal({ projectName, onClose, onReset }: ResetProjec )}

- Are you sure you want to reset {projectName}? + Reset {projectName} to start fresh.

+ {/* Reset Type Toggle */} +
+ + + +
+ + {/* What will be deleted */}

This will delete:

  • All features and their progress
  • Assistant chat history
  • Agent settings
  • + {fullReset && ( +
  • Prompts directory (app_spec.txt, templates)
  • + )}
+ {/* What will be preserved */}

This will preserve:

    -
  • App spec (prompts/app_spec.txt)
  • -
  • Prompt templates
  • + {!fullReset && ( + <> +
  • App spec (prompts/app_spec.txt)
  • +
  • Prompt templates
  • + + )}
  • Project registration
  • + {fullReset && ( +
  • + (You'll see the setup wizard to create a new spec) +
  • + )}
@@ -87,14 +145,18 @@ export function ResetProjectModal({ projectName, onClose, onReset }: ResetProjec diff --git a/ui/src/hooks/useProjects.ts b/ui/src/hooks/useProjects.ts index b25347e3..1c0381f2 100644 --- a/ui/src/hooks/useProjects.ts +++ b/ui/src/hooks/useProjects.ts @@ -52,8 +52,9 @@ export function useResetProject() { const queryClient = useQueryClient() return useMutation({ - mutationFn: (name: string) => api.resetProject(name), - onSuccess: (_, name) => { + mutationFn: ({ name, fullReset = false }: { name: string; fullReset?: boolean }) => + api.resetProject(name, fullReset), + onSuccess: (_, { name }) => { // Invalidate both projects and features queries queryClient.invalidateQueries({ queryKey: ['projects'] }) queryClient.invalidateQueries({ queryKey: ['features', name] }) diff --git a/ui/src/lib/api.ts b/ui/src/lib/api.ts index feab62f3..f0222431 100644 --- a/ui/src/lib/api.ts +++ b/ui/src/lib/api.ts @@ -70,10 +70,12 @@ export interface ResetProjectResponse { success: boolean message: string deleted_files: string[] + full_reset: boolean } -export async function resetProject(name: string): Promise { - return fetchJSON(`/projects/${encodeURIComponent(name)}/reset`, { +export async function resetProject(name: string, fullReset: boolean = false): Promise { + const params = fullReset ? '?full_reset=true' : '' + return fetchJSON(`/projects/${encodeURIComponent(name)}/reset${params}`, { method: 'POST', }) } diff --git a/ui/tsconfig.tsbuildinfo b/ui/tsconfig.tsbuildinfo index 8bf9c84a..133eb726 100644 --- a/ui/tsconfig.tsbuildinfo +++ b/ui/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/addfeatureform.tsx","./src/components/agentcontrol.tsx","./src/components/agentthought.tsx","./src/components/assistantchat.tsx","./src/components/assistantfab.tsx","./src/components/assistantpanel.tsx","./src/components/chatmessage.tsx","./src/components/debuglogviewer.tsx","./src/components/featurecard.tsx","./src/components/featuremodal.tsx","./src/components/folderbrowser.tsx","./src/components/kanbanboard.tsx","./src/components/kanbancolumn.tsx","./src/components/newprojectmodal.tsx","./src/components/progressdashboard.tsx","./src/components/projectselector.tsx","./src/components/questionoptions.tsx","./src/components/setupwizard.tsx","./src/components/speccreationchat.tsx","./src/components/typingindicator.tsx","./src/hooks/useassistantchat.ts","./src/hooks/usecelebration.ts","./src/hooks/usefeaturesound.ts","./src/hooks/useprojects.ts","./src/hooks/usespecchat.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/types.ts"],"version":"5.6.3"} \ No newline at end of file +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/addfeatureform.tsx","./src/components/agentcontrol.tsx","./src/components/agentthought.tsx","./src/components/assistantchat.tsx","./src/components/assistantfab.tsx","./src/components/assistantpanel.tsx","./src/components/chatmessage.tsx","./src/components/debuglogviewer.tsx","./src/components/featurecard.tsx","./src/components/featuremodal.tsx","./src/components/folderbrowser.tsx","./src/components/kanbanboard.tsx","./src/components/kanbancolumn.tsx","./src/components/newprojectmodal.tsx","./src/components/progressdashboard.tsx","./src/components/projectselector.tsx","./src/components/questionoptions.tsx","./src/components/resetprojectmodal.tsx","./src/components/setupwizard.tsx","./src/components/speccreationchat.tsx","./src/components/typingindicator.tsx","./src/hooks/useassistantchat.ts","./src/hooks/usecelebration.ts","./src/hooks/usefeaturesound.ts","./src/hooks/useprojects.ts","./src/hooks/usespecchat.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/types.ts"],"version":"5.6.3"} \ No newline at end of file From 112f661dcd04956a390d99eb4c5b5cf88625dd5b Mon Sep 17 00:00:00 2001 From: shivanathd Date: Mon, 5 Jan 2026 23:45:59 +0530 Subject: [PATCH 3/4] fix: add venv activation to start_ui.sh The script was failing with 'ModuleNotFoundError: No module named dotenv' because it wasn't activating the virtual environment before running Python. Now checks for and activates venv/bin/activate if the venv directory exists. --- start_ui.sh | 6 ++++++ 1 file changed, 6 insertions(+) mode change 100644 => 100755 start_ui.sh diff --git a/start_ui.sh b/start_ui.sh old mode 100644 new mode 100755 index 317d7448..d7a2ea79 --- a/start_ui.sh +++ b/start_ui.sh @@ -11,6 +11,12 @@ echo "" # Get the directory where this script is located SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# Activate virtual environment if it exists +if [ -d "$SCRIPT_DIR/venv" ]; then + echo "Activating virtual environment..." + source "$SCRIPT_DIR/venv/bin/activate" +fi + # Check if Python is available if ! command -v python3 &> /dev/null; then if ! command -v python &> /dev/null; then From db2a28043ec342f9095f29356f28198fe45d22d1 Mon Sep 17 00:00:00 2001 From: shivanathd Date: Mon, 5 Jan 2026 23:54:10 +0530 Subject: [PATCH 4/4] feat(ui): Show setup wizard after full project reset When a project's spec files are deleted via full reset, the UI now displays the ProjectSetupRequired component which offers: - "Create with Claude" for interactive spec generation - "Edit Templates Manually" for direct file editing Changes: - Add ProjectSetupRequired component for projects without specs - Update App.tsx to check has_spec and conditionally render setup UI - Refetch projects after setup completes to update UI state --- ui/src/App.tsx | 17 +- ui/src/components/ProjectSetupRequired.tsx | 175 +++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 ui/src/components/ProjectSetupRequired.tsx diff --git a/ui/src/App.tsx b/ui/src/App.tsx index cb6ef093..5cc5cd06 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -17,6 +17,7 @@ import { AgentThought } from './components/AgentThought' import { AssistantFAB } from './components/AssistantFAB' import { AssistantPanel } from './components/AssistantPanel' import { ResetProjectModal } from './components/ResetProjectModal' +import { ProjectSetupRequired } from './components/ProjectSetupRequired' import { Plus, Loader2, RotateCcw } from 'lucide-react' import type { Feature } from './lib/types' @@ -37,11 +38,17 @@ function App() { const [assistantOpen, setAssistantOpen] = useState(false) const [showResetModal, setShowResetModal] = useState(false) - const { data: projects, isLoading: projectsLoading } = useProjects() + const { data: projects, isLoading: projectsLoading, refetch: refetchProjects } = useProjects() const { data: features } = useFeatures(selectedProject) const { data: agentStatusData } = useAgentStatus(selectedProject) const wsState = useProjectWebSocket(selectedProject) + // Get the selected project's has_spec status + const selectedProjectData = selectedProject + ? projects?.find(p => p.name === selectedProject) + : null + const needsSetup = selectedProjectData?.has_spec === false + // Play sounds when features move between columns useFeatureSound(features) @@ -200,6 +207,14 @@ function App() { Select a project from the dropdown above or create a new one to get started.

+ ) : needsSetup ? ( + { + // Refetch projects to update has_spec status + refetchProjects() + }} + /> ) : (
{/* Progress Dashboard */} diff --git a/ui/src/components/ProjectSetupRequired.tsx b/ui/src/components/ProjectSetupRequired.tsx new file mode 100644 index 00000000..071a74c7 --- /dev/null +++ b/ui/src/components/ProjectSetupRequired.tsx @@ -0,0 +1,175 @@ +/** + * Project Setup Required Component + * + * Shown when a project exists but doesn't have a spec file (e.g., after full reset). + * Offers the same options as new project creation: Claude or manual spec. + */ + +import { useState } from 'react' +import { Bot, FileEdit, Loader2, AlertTriangle } from 'lucide-react' +import { SpecCreationChat } from './SpecCreationChat' +import { startAgent } from '../lib/api' + +type InitializerStatus = 'idle' | 'starting' | 'error' + +interface ProjectSetupRequiredProps { + projectName: string + onSetupComplete: () => void +} + +export function ProjectSetupRequired({ projectName, onSetupComplete }: ProjectSetupRequiredProps) { + const [showChat, setShowChat] = useState(false) + const [initializerStatus, setInitializerStatus] = useState('idle') + const [initializerError, setInitializerError] = useState(null) + const [yoloModeSelected, setYoloModeSelected] = useState(false) + + const handleClaudeSelect = () => { + setShowChat(true) + } + + const handleManualSelect = () => { + // For manual, just refresh to show the empty project + // User can edit prompts/app_spec.txt directly + onSetupComplete() + } + + const handleSpecComplete = async (_specPath: string, yoloMode: boolean = false) => { + setYoloModeSelected(yoloMode) + setInitializerStatus('starting') + try { + await startAgent(projectName, yoloMode) + onSetupComplete() + } catch (err) { + setInitializerStatus('error') + setInitializerError(err instanceof Error ? err.message : 'Failed to start agent') + } + } + + const handleRetryInitializer = () => { + setInitializerError(null) + setInitializerStatus('idle') + handleSpecComplete('', yoloModeSelected) + } + + const handleChatCancel = () => { + setShowChat(false) + } + + const handleExitToProject = () => { + onSetupComplete() + } + + // Full-screen chat view + if (showChat) { + return ( +
+ +
+ ) + } + + return ( +
+ {/* Header */} +
+
+ +
+
+

Setup Required

+

+ Project {projectName} needs an app specification to get started. +

+
+
+ + {/* Options */} +
+ {/* Claude option */} + + + {/* Manual option */} + +
+ + {initializerStatus === 'starting' && ( +
+ + Starting agent... +
+ )} + + {initializerError && ( +
+

Failed to start agent

+

{initializerError}

+ +
+ )} +
+ ) +}