# Knowledge View Task Consolidation — Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Consolidate tasks into the Knowledge view as a fifth card type, deprecate `/notes` and `/tasks` list routes, and simplify navigation down to a single Knowledge hub. **Architecture:** The backend knowledge service (`services/knowledge.py`) stops excluding tasks from queries and adds `type=task` filtering via the `is_task` property (`Note.status IS NOT NULL`). The knowledge route validation gains `"task"` as a valid type. The frontend KnowledgeView gains task card rendering with status/priority/due-date badges. Router redirects replace the deleted list views. **Tech Stack:** Python/Quart backend (SQLAlchemy), Vue 3 + TypeScript frontend, Pinia stores, Vue Router. --- ## File Map | Action | Path | |--------|------| | Modify | `src/fabledassistant/services/knowledge.py` | | Modify | `src/fabledassistant/routes/knowledge.py` | | Modify | `frontend/src/views/KnowledgeView.vue` | | Modify | `frontend/src/router/index.ts` | | Modify | `frontend/src/components/AppHeader.vue` | | Modify | `frontend/src/App.vue` | | Delete | `frontend/src/views/NotesListView.vue` | | Delete | `frontend/src/views/TasksListView.vue` | --- ### Task 1: Backend — Include tasks in knowledge queries **Files:** - Modify: `src/fabledassistant/services/knowledge.py` - Modify: `src/fabledassistant/routes/knowledge.py` **Context:** The knowledge service currently excludes tasks by filtering `Note.status.is_(None)`. Every query function (`query_knowledge`, `query_knowledge_ids`, `_semantic_knowledge_search`, `get_knowledge_tags`, `get_knowledge_counts`) has this exclusion. Adding task support means: (1) removing the task exclusion from the "all types" queries, (2) adding `type=task` as a filter option that maps to `Note.status.isnot(None)`, (3) enriching `_note_to_item` with task-specific fields, (4) updating counts to include tasks. - [ ] **Step 1: Add `"task"` to `_VALID_TYPES` in the route file** In `src/fabledassistant/routes/knowledge.py`, change: ```python _VALID_TYPES = {"note", "person", "place", "list"} ``` to: ```python _VALID_TYPES = {"note", "person", "place", "list", "task"} ``` - [ ] **Step 2: Update `_note_to_item` to include task fields** In `src/fabledassistant/services/knowledge.py`, the `_note_to_item` function builds the item dict. After the existing `elif note.entity_type == "list":` block (which ends around line 48), add a task branch. Find: ```python elif note.entity_type == "list": # Parse markdown task list syntax into structured items body = note.body or "" list_items = [] for line in body.split("\n"): stripped = line.strip() if stripped.startswith("- [ ] ") or stripped.startswith("- [x] ") or stripped.startswith("- [X] "): checked_item = not stripped.startswith("- [ ] ") list_items.append({"text": stripped[6:], "checked": checked_item}) item["list_items"] = list_items item["item_count"] = len(list_items) item["checked_count"] = sum(1 for i in list_items if i["checked"]) item["body"] = body return item ``` Replace with: ```python elif note.entity_type == "list": # Parse markdown task list syntax into structured items body = note.body or "" list_items = [] for line in body.split("\n"): stripped = line.strip() if stripped.startswith("- [ ] ") or stripped.startswith("- [x] ") or stripped.startswith("- [X] "): checked_item = not stripped.startswith("- [ ] ") list_items.append({"text": stripped[6:], "checked": checked_item}) item["list_items"] = list_items item["item_count"] = len(list_items) item["checked_count"] = sum(1 for i in list_items if i["checked"]) item["body"] = body # Task fields — included for all items but only meaningful when is_task if note.is_task: item["note_type"] = "task" item["status"] = note.status item["priority"] = note.priority item["due_date"] = note.due_date.isoformat() if note.due_date else None return item ``` This overrides `note_type` to `"task"` for task items (since `entity_type` returns the `note_type` column which is `"note"` for tasks) and adds status/priority/due_date fields. - [ ] **Step 3: Update `query_knowledge` to include tasks** In the `query_knowledge` function, the "all types" filter currently excludes tasks. Change the base query and the `else` branch. Find: ```python base = ( select(Note) .where(Note.user_id == user_id) .where(Note.status.is_(None)) # exclude tasks ) if note_type: base = base.where(Note.note_type == note_type) else: # Exclude tasks — already done above; also exclude any legacy nulls base = base.where(Note.note_type.in_(["note", "person", "place", "list"])) ``` Replace with: ```python base = select(Note).where(Note.user_id == user_id) if note_type == "task": base = base.where(Note.status.isnot(None)) elif note_type: base = base.where(Note.note_type == note_type).where(Note.status.is_(None)) else: # All types including tasks pass ``` - [ ] **Step 4: Update `_semantic_knowledge_search` to include tasks** Find: ```python candidates = await semantic_search_notes( user_id=user_id, query=q, limit=min(200, limit * 8), threshold=0.3, is_task=False, ) ``` Replace with: ```python is_task_filter = True if note_type == "task" else (False if note_type else None) candidates = await semantic_search_notes( user_id=user_id, query=q, limit=min(200, limit * 8), threshold=0.3, is_task=is_task_filter, ) ``` Also update the type matching in the filter loop — find: ```python for _score, note in candidates: if note_type and note.entity_type != note_type: continue ``` Replace with: ```python for _score, note in candidates: if note_type == "task" and not note.is_task: continue elif note_type and note_type != "task" and note.entity_type != note_type: continue ``` - [ ] **Step 5: Update `query_knowledge_ids` to include tasks** Find: ```python base = ( select(Note.id) .where(Note.user_id == user_id) .where(Note.status.is_(None)) ) if note_type: base = base.where(Note.note_type == note_type) else: base = base.where(Note.note_type.in_(["note", "person", "place", "list"])) ``` Replace with: ```python base = select(Note.id).where(Note.user_id == user_id) if note_type == "task": base = base.where(Note.status.isnot(None)) elif note_type: base = base.where(Note.note_type == note_type).where(Note.status.is_(None)) else: pass ``` - [ ] **Step 6: Update `get_knowledge_tags` to include task tags** Find: ```python base = ( select(func.unnest(Note.tags).label("tag")) .where(Note.user_id == user_id) .where(Note.status.is_(None)) ) if note_type: base = base.where(Note.note_type == note_type) else: base = base.where(Note.note_type.in_(["note", "person", "place", "list"])) ``` Replace with: ```python base = ( select(func.unnest(Note.tags).label("tag")) .where(Note.user_id == user_id) ) if note_type == "task": base = base.where(Note.status.isnot(None)) elif note_type: base = base.where(Note.note_type == note_type).where(Note.status.is_(None)) else: pass ``` - [ ] **Step 7: Update `get_knowledge_counts` to include tasks** Find: ```python async with async_session() as session: stmt = ( select(Note.note_type, func.count(Note.id)) .where(Note.user_id == user_id) .where(Note.status.is_(None)) .where(Note.note_type.in_(["note", "person", "place", "list"])) .group_by(Note.note_type) ) if tags: for tag in tags: stmt = stmt.where(Note.tags.contains([tag])) rows = list((await session.execute(stmt)).all()) counts = {row[0]: row[1] for row in rows} # Ensure all types present even if zero for t in ("note", "person", "place", "list"): counts.setdefault(t, 0) counts["total"] = sum(counts[t] for t in ("note", "person", "place", "list")) return counts ``` Replace with: ```python async with async_session() as session: # Count non-task types stmt = ( select(Note.note_type, func.count(Note.id)) .where(Note.user_id == user_id) .where(Note.status.is_(None)) .where(Note.note_type.in_(["note", "person", "place", "list"])) .group_by(Note.note_type) ) if tags: for tag in tags: stmt = stmt.where(Note.tags.contains([tag])) rows = list((await session.execute(stmt)).all()) counts = {row[0]: row[1] for row in rows} # Count tasks separately (is_task = status IS NOT NULL) task_stmt = ( select(func.count(Note.id)) .where(Note.user_id == user_id) .where(Note.status.isnot(None)) ) if tags: for tag in tags: task_stmt = task_stmt.where(Note.tags.contains([tag])) task_count: int = (await session.execute(task_stmt)).scalar_one() counts["task"] = task_count for t in ("note", "person", "place", "list", "task"): counts.setdefault(t, 0) counts["total"] = sum(counts[t] for t in ("note", "person", "place", "list", "task")) return counts ``` - [ ] **Step 8: Verify backend changes** ```bash cd /path/to/fabledassistant make typecheck make test ``` Expected: no errors. - [ ] **Step 9: Commit** ```bash git add src/fabledassistant/services/knowledge.py src/fabledassistant/routes/knowledge.py git commit -m "feat(knowledge): include tasks in knowledge queries and counts" ``` --- ### Task 2: Frontend — Task card rendering in KnowledgeView **Files:** - Modify: `frontend/src/views/KnowledgeView.vue` **Context:** `KnowledgeView.vue` has a `KnowledgeItem` interface and renders cards in a grid. Each card type has type-specific content (person shows relationship/email, list shows checkboxes, etc.). Task cards need status, priority, and due date display. The `activeType` ref controls filtering; it needs `"task"` as a valid value. The type filter sidebar needs a "Tasks" button. The new-note button interaction changes from split-button to toggle. - [ ] **Step 1: Add `"task"` to the KnowledgeItem interface and filter type** In the `