From f3fc87e7151d39063639820610e1cf2c9da6fbc6 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Nov 2025 01:50:23 +0000 Subject: [PATCH 1/2] Fix tree view indentation and kanban subtask handling Tree View: - Fix nested task indentation to scale properly with depth (1.5rem per level) - Sub-subtasks now indent correctly relative to their parents Kanban View: - Show ALL tasks as draggable cards (not just root tasks) - Subtasks display parent task name for context - Each subtask can be independently dragged to different status columns - Remove nested subtask expansion since all tasks are now individual cards --- frontend/src/components/KanbanView.jsx | 48 +++++++++----------------- frontend/src/components/TreeView.jsx | 7 ++-- 2 files changed, 19 insertions(+), 36 deletions(-) diff --git a/frontend/src/components/KanbanView.jsx b/frontend/src/components/KanbanView.jsx index e8d7214..9bc9f5f 100644 --- a/frontend/src/components/KanbanView.jsx +++ b/frontend/src/components/KanbanView.jsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { Plus, Edit2, Trash2, Check, X, ChevronDown, ChevronRight } from 'lucide-react' +import { Plus, Edit2, Trash2, Check, X } from 'lucide-react' import { getProjectTasks, createTask, @@ -17,10 +17,11 @@ const STATUSES = [ function TaskCard({ task, allTasks, onUpdate, onDragStart }) { const [isEditing, setIsEditing] = useState(false) const [editTitle, setEditTitle] = useState(task.title) - const [showSubtasks, setShowSubtasks] = useState(false) - const subtasks = allTasks.filter(t => t.parent_task_id === task.id) - const hasSubtasks = subtasks.length > 0 + // Find parent task if this is a subtask + const parentTask = task.parent_task_id + ? allTasks.find(t => t.id === task.parent_task_id) + : null const handleSave = async () => { try { @@ -75,8 +76,15 @@ function TaskCard({ task, allTasks, onUpdate, onDragStart }) { ) : ( <> -
- {task.title} +
+
+
{task.title}
+ {parentTask && ( +
+ ↳ subtask of: {parentTask.title} +
+ )} +
- - {hasSubtasks && ( -
- - {showSubtasks && ( -
- {subtasks.map(subtask => ( -
- • {subtask.title} -
- ))} -
- )} -
- )} )}
@@ -255,9 +242,6 @@ function KanbanView({ projectId }) { return
{error}
} - // Only show root-level tasks in Kanban (tasks without parents) - const rootTasks = allTasks.filter(t => !t.parent_task_id) - return (

Kanban Board

@@ -267,7 +251,7 @@ function KanbanView({ projectId }) { t.status === status.key)} + tasks={allTasks.filter(t => t.status === status.key)} allTasks={allTasks} projectId={projectId} onUpdate={loadTasks} @@ -277,7 +261,7 @@ function KanbanView({ projectId }) { ))}
- {rootTasks.length === 0 && ( + {allTasks.length === 0 && (

No tasks yet

Add tasks using the + button in any column

diff --git a/frontend/src/components/TreeView.jsx b/frontend/src/components/TreeView.jsx index bb53f7b..0c3a9b9 100644 --- a/frontend/src/components/TreeView.jsx +++ b/frontend/src/components/TreeView.jsx @@ -85,9 +85,8 @@ function TaskNode({ task, projectId, onUpdate, level = 0 }) { return (
0 ? 'ml-6' : '' - }`} + style={{ marginLeft: `${level * 1.5}rem` }} + className="flex items-center gap-2 p-3 bg-cyber-darkest border border-cyber-orange/20 rounded hover:border-cyber-orange/40 transition-all group" > {/* Expand/Collapse */} {hasSubtasks && ( @@ -176,7 +175,7 @@ function TaskNode({ task, projectId, onUpdate, level = 0 }) { {/* Add Subtask Form */} {showAddSubtask && ( -
0 ? 'ml-6' : ''}`}> +
Date: Thu, 20 Nov 2025 04:54:01 +0000 Subject: [PATCH 2/2] Add v0.1.3 backend features: metadata fields and search Backend Changes: - Add estimated_minutes field to tasks (stored as integer minutes) - Add tags field (JSON array) for categorizing tasks - Add flag_color field for visual priority indicators - Add search endpoint (/api/search) with project filtering - Update JSON import to handle new metadata fields Frontend Changes: - Display version v0.1.3 in header - Add search API client function - Add format utility for time display (30m, 1.5h, etc.) Example Data: - Update example-import.json with time estimates, tags, and flags - Demonstrate nested metadata inheritance Note: Frontend UI for displaying/editing these fields in progress --- backend/app/main.py | 44 ++++++++++++++++++++ backend/app/models.py | 5 ++- backend/app/schemas.py | 9 ++++ example-import.json | 79 +++++++++++++++++++++++++++++------- frontend/src/App.jsx | 13 ++++-- frontend/src/utils/api.js | 9 ++++ frontend/src/utils/format.js | 17 ++++++++ 7 files changed, 156 insertions(+), 20 deletions(-) create mode 100644 frontend/src/utils/format.js diff --git a/backend/app/main.py b/backend/app/main.py index 5e0f94d..7640ff3 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -144,6 +144,47 @@ def delete_task(task_id: int, db: Session = Depends(get_db)): return None +# ========== SEARCH ENDPOINT ========== + +@app.get("/api/search", response_model=List[schemas.Task]) +def search_tasks( + query: str, + project_ids: Optional[str] = None, + db: Session = Depends(get_db) +): + """ + Search tasks across projects by title, description, and tags. + + Args: + query: Search term to match against title, description, and tags + project_ids: Comma-separated list of project IDs to search in (optional, searches all if not provided) + """ + # Parse project IDs if provided + project_id_list = None + if project_ids: + try: + project_id_list = [int(pid.strip()) for pid in project_ids.split(',') if pid.strip()] + except ValueError: + raise HTTPException(status_code=400, detail="Invalid project_ids format") + + # Build query + tasks_query = db.query(models.Task) + + # Filter by project IDs if specified + if project_id_list: + tasks_query = tasks_query.filter(models.Task.project_id.in_(project_id_list)) + + # Search in title, description, and tags + search_term = f"%{query}%" + tasks = tasks_query.filter( + (models.Task.title.ilike(search_term)) | + (models.Task.description.ilike(search_term)) | + (models.Task.tags.contains([query])) # Exact tag match + ).all() + + return tasks + + # ========== JSON IMPORT ENDPOINT ========== def _import_tasks_recursive( @@ -161,6 +202,9 @@ def _import_tasks_recursive( title=task_data.title, description=task_data.description, status=task_data.status, + estimated_minutes=task_data.estimated_minutes, + tags=task_data.tags, + flag_color=task_data.flag_color, sort_order=idx ) db_task = crud.create_task(db, task) diff --git a/backend/app/models.py b/backend/app/models.py index 350b875..47455c5 100644 --- a/backend/app/models.py +++ b/backend/app/models.py @@ -1,4 +1,4 @@ -from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Enum +from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Enum, JSON from sqlalchemy.orm import relationship from datetime import datetime import enum @@ -34,6 +34,9 @@ class Task(Base): description = Column(Text, nullable=True) status = Column(Enum(TaskStatus), default=TaskStatus.BACKLOG, nullable=False) sort_order = Column(Integer, default=0) + estimated_minutes = Column(Integer, nullable=True) + tags = Column(JSON, nullable=True) + flag_color = Column(String(50), nullable=True) created_at = Column(DateTime, default=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) diff --git a/backend/app/schemas.py b/backend/app/schemas.py index f04e3d2..00aa35d 100644 --- a/backend/app/schemas.py +++ b/backend/app/schemas.py @@ -11,6 +11,9 @@ class TaskBase(BaseModel): status: TaskStatus = TaskStatus.BACKLOG parent_task_id: Optional[int] = None sort_order: int = 0 + estimated_minutes: Optional[int] = None + tags: Optional[List[str]] = None + flag_color: Optional[str] = None class TaskCreate(TaskBase): @@ -23,6 +26,9 @@ class TaskUpdate(BaseModel): status: Optional[TaskStatus] = None parent_task_id: Optional[int] = None sort_order: Optional[int] = None + estimated_minutes: Optional[int] = None + tags: Optional[List[str]] = None + flag_color: Optional[str] = None class Task(TaskBase): @@ -74,6 +80,9 @@ class ImportSubtask(BaseModel): title: str description: Optional[str] = None status: TaskStatus = TaskStatus.BACKLOG + estimated_minutes: Optional[int] = None + tags: Optional[List[str]] = None + flag_color: Optional[str] = None subtasks: List['ImportSubtask'] = [] diff --git a/example-import.json b/example-import.json index 22b1046..7742a18 100644 --- a/example-import.json +++ b/example-import.json @@ -8,32 +8,48 @@ "title": "Cortex Rewire", "description": "Refactor reasoning layer for improved performance", "status": "backlog", + "estimated_minutes": 240, + "tags": ["coding", "backend", "refactoring"], + "flag_color": "red", "subtasks": [ { "title": "Reflection → fix backend argument bug", "status": "in_progress", + "estimated_minutes": 90, + "tags": ["coding", "bug-fix"], + "flag_color": "orange", "subtasks": [ { "title": "Normalize LLM backend arg in reflection calls", - "status": "in_progress" + "status": "in_progress", + "estimated_minutes": 45, + "tags": ["coding"] }, { "title": "Add unit tests for reflection module", - "status": "backlog" + "status": "backlog", + "estimated_minutes": 60, + "tags": ["testing", "coding"] } ] }, { "title": "Reasoning parser cleanup", "status": "backlog", + "estimated_minutes": 120, + "tags": ["coding", "cleanup"], "subtasks": [ { "title": "Remove deprecated parse methods", - "status": "backlog" + "status": "backlog", + "estimated_minutes": 30, + "tags": ["coding"] }, { "title": "Optimize regex patterns", - "status": "backlog" + "status": "backlog", + "estimated_minutes": 45, + "tags": ["coding", "performance"] } ] } @@ -43,32 +59,47 @@ "title": "Frontend Overhaul", "description": "Modernize the UI with new component library", "status": "backlog", + "estimated_minutes": 480, + "tags": ["frontend", "ui", "coding"], + "flag_color": "blue", "subtasks": [ { "title": "Migrate to Tailwind CSS", - "status": "backlog" + "status": "backlog", + "estimated_minutes": 180, + "tags": ["frontend", "styling"] }, { "title": "Build new component library", "status": "backlog", + "estimated_minutes": 360, + "tags": ["frontend", "components"], "subtasks": [ { "title": "Button components", - "status": "backlog" + "status": "backlog", + "estimated_minutes": 60, + "tags": ["frontend", "components"] }, { "title": "Form components", - "status": "backlog" + "status": "backlog", + "estimated_minutes": 120, + "tags": ["frontend", "components"] }, { "title": "Modal components", - "status": "backlog" + "status": "backlog", + "estimated_minutes": 90, + "tags": ["frontend", "components"] } ] }, { "title": "Implement dark mode toggle", - "status": "backlog" + "status": "backlog", + "estimated_minutes": 45, + "tags": ["frontend", "ui"] } ] }, @@ -76,36 +107,54 @@ "title": "API v2 Implementation", "status": "blocked", "description": "Blocked on database migration completion", + "estimated_minutes": 600, + "tags": ["backend", "api", "coding"], + "flag_color": "yellow", "subtasks": [ { "title": "Design new REST endpoints", - "status": "done" + "status": "done", + "estimated_minutes": 120, + "tags": ["design", "api"] }, { "title": "Implement GraphQL layer", - "status": "blocked" + "status": "blocked", + "estimated_minutes": 300, + "tags": ["backend", "graphql", "coding"] }, { "title": "Add rate limiting", - "status": "backlog" + "status": "backlog", + "estimated_minutes": 90, + "tags": ["backend", "security"] } ] }, { "title": "Documentation Sprint", "status": "done", + "estimated_minutes": 180, + "tags": ["documentation", "writing"], + "flag_color": "green", "subtasks": [ { "title": "API documentation", - "status": "done" + "status": "done", + "estimated_minutes": 60, + "tags": ["documentation"] }, { "title": "User guide", - "status": "done" + "status": "done", + "estimated_minutes": 90, + "tags": ["documentation", "tutorial"] }, { "title": "Developer setup guide", - "status": "done" + "status": "done", + "estimated_minutes": 30, + "tags": ["documentation"] } ] } diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 5533d68..79d44d8 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -7,10 +7,15 @@ function App() {
-

- TESSERACT - Task Decomposition Engine -

+
+
+

+ TESSERACT + Task Decomposition Engine + v0.1.3 +

+
+
diff --git a/frontend/src/utils/api.js b/frontend/src/utils/api.js index 14247e1..4805c54 100644 --- a/frontend/src/utils/api.js +++ b/frontend/src/utils/api.js @@ -56,3 +56,12 @@ export const importJSON = (data) => fetchAPI('/import-json', { method: 'POST', body: JSON.stringify(data), }); + +// Search +export const searchTasks = (query, projectIds = null) => { + const params = new URLSearchParams({ query }); + if (projectIds && projectIds.length > 0) { + params.append('project_ids', projectIds.join(',')); + } + return fetchAPI(`/search?${params.toString()}`); +}; diff --git a/frontend/src/utils/format.js b/frontend/src/utils/format.js new file mode 100644 index 0000000..65a30dc --- /dev/null +++ b/frontend/src/utils/format.js @@ -0,0 +1,17 @@ +// Format minutes into display string +export function formatTime(minutes) { + if (!minutes || minutes === 0) return null; + + if (minutes < 60) { + return `${minutes}m`; + } + + const hours = minutes / 60; + return `${hours.toFixed(1)}h`; +} + +// Format tags as comma-separated string +export function formatTags(tags) { + if (!tags || tags.length === 0) return null; + return tags.join(', '); +}