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
This commit is contained in:
Claude
2025-11-20 04:54:01 +00:00
parent f3fc87e715
commit 5d43dc6fd1
7 changed files with 156 additions and 20 deletions

View File

@@ -144,6 +144,47 @@ def delete_task(task_id: int, db: Session = Depends(get_db)):
return None 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 ========== # ========== JSON IMPORT ENDPOINT ==========
def _import_tasks_recursive( def _import_tasks_recursive(
@@ -161,6 +202,9 @@ def _import_tasks_recursive(
title=task_data.title, title=task_data.title,
description=task_data.description, description=task_data.description,
status=task_data.status, status=task_data.status,
estimated_minutes=task_data.estimated_minutes,
tags=task_data.tags,
flag_color=task_data.flag_color,
sort_order=idx sort_order=idx
) )
db_task = crud.create_task(db, task) db_task = crud.create_task(db, task)

View File

@@ -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 sqlalchemy.orm import relationship
from datetime import datetime from datetime import datetime
import enum import enum
@@ -34,6 +34,9 @@ class Task(Base):
description = Column(Text, nullable=True) description = Column(Text, nullable=True)
status = Column(Enum(TaskStatus), default=TaskStatus.BACKLOG, nullable=False) status = Column(Enum(TaskStatus), default=TaskStatus.BACKLOG, nullable=False)
sort_order = Column(Integer, default=0) 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) created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

View File

@@ -11,6 +11,9 @@ class TaskBase(BaseModel):
status: TaskStatus = TaskStatus.BACKLOG status: TaskStatus = TaskStatus.BACKLOG
parent_task_id: Optional[int] = None parent_task_id: Optional[int] = None
sort_order: int = 0 sort_order: int = 0
estimated_minutes: Optional[int] = None
tags: Optional[List[str]] = None
flag_color: Optional[str] = None
class TaskCreate(TaskBase): class TaskCreate(TaskBase):
@@ -23,6 +26,9 @@ class TaskUpdate(BaseModel):
status: Optional[TaskStatus] = None status: Optional[TaskStatus] = None
parent_task_id: Optional[int] = None parent_task_id: Optional[int] = None
sort_order: 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): class Task(TaskBase):
@@ -74,6 +80,9 @@ class ImportSubtask(BaseModel):
title: str title: str
description: Optional[str] = None description: Optional[str] = None
status: TaskStatus = TaskStatus.BACKLOG status: TaskStatus = TaskStatus.BACKLOG
estimated_minutes: Optional[int] = None
tags: Optional[List[str]] = None
flag_color: Optional[str] = None
subtasks: List['ImportSubtask'] = [] subtasks: List['ImportSubtask'] = []

View File

@@ -8,32 +8,48 @@
"title": "Cortex Rewire", "title": "Cortex Rewire",
"description": "Refactor reasoning layer for improved performance", "description": "Refactor reasoning layer for improved performance",
"status": "backlog", "status": "backlog",
"estimated_minutes": 240,
"tags": ["coding", "backend", "refactoring"],
"flag_color": "red",
"subtasks": [ "subtasks": [
{ {
"title": "Reflection → fix backend argument bug", "title": "Reflection → fix backend argument bug",
"status": "in_progress", "status": "in_progress",
"estimated_minutes": 90,
"tags": ["coding", "bug-fix"],
"flag_color": "orange",
"subtasks": [ "subtasks": [
{ {
"title": "Normalize LLM backend arg in reflection calls", "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", "title": "Add unit tests for reflection module",
"status": "backlog" "status": "backlog",
"estimated_minutes": 60,
"tags": ["testing", "coding"]
} }
] ]
}, },
{ {
"title": "Reasoning parser cleanup", "title": "Reasoning parser cleanup",
"status": "backlog", "status": "backlog",
"estimated_minutes": 120,
"tags": ["coding", "cleanup"],
"subtasks": [ "subtasks": [
{ {
"title": "Remove deprecated parse methods", "title": "Remove deprecated parse methods",
"status": "backlog" "status": "backlog",
"estimated_minutes": 30,
"tags": ["coding"]
}, },
{ {
"title": "Optimize regex patterns", "title": "Optimize regex patterns",
"status": "backlog" "status": "backlog",
"estimated_minutes": 45,
"tags": ["coding", "performance"]
} }
] ]
} }
@@ -43,32 +59,47 @@
"title": "Frontend Overhaul", "title": "Frontend Overhaul",
"description": "Modernize the UI with new component library", "description": "Modernize the UI with new component library",
"status": "backlog", "status": "backlog",
"estimated_minutes": 480,
"tags": ["frontend", "ui", "coding"],
"flag_color": "blue",
"subtasks": [ "subtasks": [
{ {
"title": "Migrate to Tailwind CSS", "title": "Migrate to Tailwind CSS",
"status": "backlog" "status": "backlog",
"estimated_minutes": 180,
"tags": ["frontend", "styling"]
}, },
{ {
"title": "Build new component library", "title": "Build new component library",
"status": "backlog", "status": "backlog",
"estimated_minutes": 360,
"tags": ["frontend", "components"],
"subtasks": [ "subtasks": [
{ {
"title": "Button components", "title": "Button components",
"status": "backlog" "status": "backlog",
"estimated_minutes": 60,
"tags": ["frontend", "components"]
}, },
{ {
"title": "Form components", "title": "Form components",
"status": "backlog" "status": "backlog",
"estimated_minutes": 120,
"tags": ["frontend", "components"]
}, },
{ {
"title": "Modal components", "title": "Modal components",
"status": "backlog" "status": "backlog",
"estimated_minutes": 90,
"tags": ["frontend", "components"]
} }
] ]
}, },
{ {
"title": "Implement dark mode toggle", "title": "Implement dark mode toggle",
"status": "backlog" "status": "backlog",
"estimated_minutes": 45,
"tags": ["frontend", "ui"]
} }
] ]
}, },
@@ -76,36 +107,54 @@
"title": "API v2 Implementation", "title": "API v2 Implementation",
"status": "blocked", "status": "blocked",
"description": "Blocked on database migration completion", "description": "Blocked on database migration completion",
"estimated_minutes": 600,
"tags": ["backend", "api", "coding"],
"flag_color": "yellow",
"subtasks": [ "subtasks": [
{ {
"title": "Design new REST endpoints", "title": "Design new REST endpoints",
"status": "done" "status": "done",
"estimated_minutes": 120,
"tags": ["design", "api"]
}, },
{ {
"title": "Implement GraphQL layer", "title": "Implement GraphQL layer",
"status": "blocked" "status": "blocked",
"estimated_minutes": 300,
"tags": ["backend", "graphql", "coding"]
}, },
{ {
"title": "Add rate limiting", "title": "Add rate limiting",
"status": "backlog" "status": "backlog",
"estimated_minutes": 90,
"tags": ["backend", "security"]
} }
] ]
}, },
{ {
"title": "Documentation Sprint", "title": "Documentation Sprint",
"status": "done", "status": "done",
"estimated_minutes": 180,
"tags": ["documentation", "writing"],
"flag_color": "green",
"subtasks": [ "subtasks": [
{ {
"title": "API documentation", "title": "API documentation",
"status": "done" "status": "done",
"estimated_minutes": 60,
"tags": ["documentation"]
}, },
{ {
"title": "User guide", "title": "User guide",
"status": "done" "status": "done",
"estimated_minutes": 90,
"tags": ["documentation", "tutorial"]
}, },
{ {
"title": "Developer setup guide", "title": "Developer setup guide",
"status": "done" "status": "done",
"estimated_minutes": 30,
"tags": ["documentation"]
} }
] ]
} }

View File

@@ -7,10 +7,15 @@ function App() {
<div className="min-h-screen bg-cyber-dark"> <div className="min-h-screen bg-cyber-dark">
<header className="border-b border-cyber-orange/30 bg-cyber-darkest"> <header className="border-b border-cyber-orange/30 bg-cyber-darkest">
<div className="container mx-auto px-4 py-4"> <div className="container mx-auto px-4 py-4">
<h1 className="text-2xl font-bold text-cyber-orange"> <div className="flex justify-between items-center">
TESSERACT <div>
<span className="ml-3 text-sm text-gray-500">Task Decomposition Engine</span> <h1 className="text-2xl font-bold text-cyber-orange">
</h1> TESSERACT
<span className="ml-3 text-sm text-gray-500">Task Decomposition Engine</span>
<span className="ml-2 text-xs text-gray-600">v0.1.3</span>
</h1>
</div>
</div>
</div> </div>
</header> </header>

View File

@@ -56,3 +56,12 @@ export const importJSON = (data) => fetchAPI('/import-json', {
method: 'POST', method: 'POST',
body: JSON.stringify(data), 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()}`);
};

View File

@@ -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(', ');
}