diff --git a/docs/superpowers/plans/2026-04-04-knowledge-view.md b/docs/superpowers/plans/2026-04-04-knowledge-view.md
new file mode 100644
index 0000000..ac2fe0c
--- /dev/null
+++ b/docs/superpowers/plans/2026-04-04-knowledge-view.md
@@ -0,0 +1,2344 @@
+# Knowledge View 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:** Replace the Library tab with a typed Knowledge view, add a Projects tab, and wire a two-tier paginated feed (IDs + batch hydration) backed by `/api/knowledge`.
+
+**Architecture:** Four-tab shell (Briefing / Knowledge / Chat / Projects). Knowledge screen fetches up to 50 IDs per page then hydrates 12 items at a time via batch endpoint. Type tabs filter server-side. Projects screen lists all projects sorted by `updated_at desc` with a dedicated create/edit screen. All state managed with Riverpod StateNotifier / AsyncNotifier following existing app patterns.
+
+**Tech Stack:** Flutter 3, Riverpod 3, GoRouter 17, Dio 5. No new pubspec dependencies.
+
+---
+
+## Manifest Pre-Check (do this before Task 1)
+
+Open `android/app/src/main/AndroidManifest.xml` and confirm all three of the following are present. **If any are missing, add them before proceeding.**
+
+- `` — already present ✓
+- `` — already present ✓
+- `android:usesCleartextTraffic="true"` on the `` tag — already present ✓
+
+No manifest changes required for this feature.
+
+---
+
+## File Map
+
+| Action | File |
+|---|---|
+| Modify | `lib/core/constants.dart` |
+| Modify | `lib/data/models/note.dart` |
+| Modify | `lib/data/api/notes_api.dart` |
+| Modify | `lib/data/repositories/notes_repository.dart` |
+| Modify | `lib/providers/notes_provider.dart` |
+| Modify | `lib/screens/notes/note_edit_screen.dart` |
+| Create | `lib/data/models/knowledge_item.dart` |
+| Create | `lib/data/api/knowledge_api.dart` |
+| Create | `lib/data/repositories/knowledge_repository.dart` |
+| Modify | `lib/providers/api_client_provider.dart` |
+| Create | `lib/providers/knowledge_provider.dart` |
+| Create | `lib/widgets/knowledge_item_card.dart` |
+| Create | `lib/screens/knowledge/knowledge_screen.dart` |
+| Modify | `lib/data/api/projects_api.dart` |
+| Modify | `lib/data/repositories/projects_repository.dart` |
+| Modify | `lib/providers/projects_provider.dart` |
+| Create | `lib/screens/projects/projects_screen.dart` |
+| Create | `lib/screens/projects/project_edit_screen.dart` |
+| Modify | `lib/screens/library/project_tasks_screen.dart` |
+| Modify | `lib/app.dart` |
+| Delete | `lib/screens/library/library_screen.dart` |
+| Delete | `lib/widgets/library_item_card.dart` |
+
+---
+
+## Task 1: Route Constants
+
+**Files:**
+- Modify: `lib/core/constants.dart`
+
+- [ ] **Step 1: Update constants**
+
+Replace the contents of `lib/core/constants.dart` with:
+
+```dart
+abstract class Routes {
+ static const splash = '/';
+ static const setup = '/setup';
+ static const login = '/login';
+ static const notes = '/notes';
+ static const noteDetail = '/notes/:id';
+ static const noteEdit = '/notes/:id/edit';
+ static const noteNew = '/notes/new';
+ static const tasks = '/tasks';
+ static const taskNew = '/tasks/new';
+ static const taskEdit = '/tasks/:id/edit';
+ static const knowledge = '/knowledge';
+ static const projects = '/projects';
+ static const projectEdit = '/projects/:id/edit';
+ static const conversations = '/chat';
+ static const chat = '/chat/:id';
+ static const quickCapture = '/quick-capture';
+ static const settings = '/settings';
+ static const briefing = '/briefing';
+ static const projectTasks = '/projects/:id/tasks';
+}
+```
+
+Changes: `library` removed, `knowledge` added, `projectEdit` added.
+
+- [ ] **Step 2: Verify**
+
+```bash
+cd /home/bvandeusen/Nextcloud/Projects/fabled_app
+flutter analyze lib/core/constants.dart
+```
+
+Expected: `No issues found!`
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add lib/core/constants.dart
+git commit -m "feat(knowledge): add knowledge + projectEdit route constants"
+```
+
+---
+
+## Task 2: Note Model — Add noteType
+
+**Files:**
+- Modify: `lib/data/models/note.dart`
+- Modify: `lib/data/api/notes_api.dart`
+- Modify: `lib/data/repositories/notes_repository.dart`
+- Modify: `lib/providers/notes_provider.dart`
+- Modify: `test/widget_test.dart`
+
+- [ ] **Step 1: Write a failing test**
+
+Replace `test/widget_test.dart` with:
+
+```dart
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ group('Note.fromJson', () {
+ test('parses noteType when present', () {
+ final json = {
+ 'id': 1,
+ 'title': 'Alice',
+ 'body': '',
+ 'tags': [],
+ 'note_type': 'person',
+ 'created_at': '2024-01-01T00:00:00',
+ 'updated_at': '2024-01-01T00:00:00',
+ };
+ // Note class is not imported yet — this will fail to compile
+ // until Task 2 Step 2 is complete.
+ });
+ });
+}
+```
+
+Run to confirm it fails:
+
+```bash
+flutter test test/widget_test.dart
+```
+
+Expected: compilation error (Note not imported) — confirms test is wired.
+
+- [ ] **Step 2: Update `lib/data/models/note.dart`**
+
+```dart
+class Note {
+ final int id;
+ final String title;
+ final String body;
+ final List tags;
+ final String noteType;
+ final int? projectId;
+ final int? milestoneId;
+ final DateTime createdAt;
+ final DateTime updatedAt;
+
+ const Note({
+ required this.id,
+ required this.title,
+ required this.body,
+ required this.tags,
+ this.noteType = 'note',
+ this.projectId,
+ this.milestoneId,
+ required this.createdAt,
+ required this.updatedAt,
+ });
+
+ factory Note.fromJson(Map json) => Note(
+ id: json['id'] as int,
+ title: json['title'] as String? ?? '',
+ body: json['body'] as String? ?? '',
+ tags: (json['tags'] as List?)
+ ?.map((e) => e as String)
+ .toList() ??
+ [],
+ noteType: json['note_type'] as String? ?? 'note',
+ projectId: json['project_id'] as int?,
+ milestoneId: json['milestone_id'] as int?,
+ createdAt: DateTime.parse(json['created_at'] as String),
+ updatedAt: DateTime.parse(json['updated_at'] as String),
+ );
+
+ Map toJson() => {
+ 'title': title,
+ 'body': body,
+ 'tags': tags,
+ 'note_type': noteType,
+ 'project_id': projectId,
+ 'milestone_id': milestoneId,
+ };
+
+ Note copyWith({
+ String? title,
+ String? body,
+ List? tags,
+ String? noteType,
+ Object? projectId = _undefined,
+ Object? milestoneId = _undefined,
+ }) =>
+ Note(
+ id: id,
+ title: title ?? this.title,
+ body: body ?? this.body,
+ tags: tags ?? this.tags,
+ noteType: noteType ?? this.noteType,
+ projectId: identical(projectId, _undefined)
+ ? this.projectId
+ : projectId as int?,
+ milestoneId: identical(milestoneId, _undefined)
+ ? this.milestoneId
+ : milestoneId as int?,
+ createdAt: createdAt,
+ updatedAt: updatedAt,
+ );
+
+ static const _undefined = Object();
+}
+```
+
+- [ ] **Step 3: Write the real test and verify it passes**
+
+Replace `test/widget_test.dart`:
+
+```dart
+import 'package:fabled_app/data/models/note.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ group('Note.fromJson', () {
+ test('parses noteType when present', () {
+ final json = {
+ 'id': 1,
+ 'title': 'Alice',
+ 'body': '',
+ 'tags': [],
+ 'note_type': 'person',
+ 'created_at': '2024-01-01T00:00:00',
+ 'updated_at': '2024-01-01T00:00:00',
+ };
+ final note = Note.fromJson(json);
+ expect(note.noteType, equals('person'));
+ });
+
+ test('defaults noteType to note when absent', () {
+ final json = {
+ 'id': 2,
+ 'title': 'My note',
+ 'body': '',
+ 'tags': [],
+ 'created_at': '2024-01-01T00:00:00',
+ 'updated_at': '2024-01-01T00:00:00',
+ };
+ final note = Note.fromJson(json);
+ expect(note.noteType, equals('note'));
+ });
+ });
+}
+```
+
+```bash
+flutter test test/widget_test.dart
+```
+
+Expected: `All tests passed!`
+
+- [ ] **Step 4: Update `lib/data/api/notes_api.dart`**
+
+Add `noteType` parameter to `create` and `update`:
+
+```dart
+import 'package:dio/dio.dart';
+
+import '../models/note.dart';
+import 'api_client.dart';
+
+class NotesApi {
+ final Dio _dio;
+ const NotesApi(this._dio);
+
+ Future> getAll() async {
+ try {
+ final response = await _dio.get('/api/notes');
+ final data = response.data as Map;
+ final list = data['notes'] as List;
+ return list.map((e) => Note.fromJson(e as Map)).toList();
+ } on DioException catch (e) {
+ throw dioToApp(e);
+ }
+ }
+
+ Future getOne(int id) async {
+ try {
+ final response = await _dio.get('/api/notes/$id');
+ return Note.fromJson(response.data as Map);
+ } on DioException catch (e) {
+ throw dioToApp(e);
+ }
+ }
+
+ Future create(
+ String title,
+ String body, {
+ List tags = const [],
+ int? projectId,
+ String noteType = 'note',
+ }) async {
+ try {
+ final response = await _dio.post('/api/notes', data: {
+ 'title': title,
+ 'body': body,
+ 'tags': tags,
+ 'note_type': noteType,
+ if (projectId != null) 'project_id': projectId,
+ });
+ return Note.fromJson(response.data as Map);
+ } on DioException catch (e) {
+ throw dioToApp(e);
+ }
+ }
+
+ Future update(
+ int id,
+ String title,
+ String body, {
+ List tags = const [],
+ int? projectId,
+ bool clearProject = false,
+ String noteType = 'note',
+ }) async {
+ try {
+ final response = await _dio.put('/api/notes/$id', data: {
+ 'title': title,
+ 'body': body,
+ 'tags': tags,
+ 'note_type': noteType,
+ if (clearProject) 'project_id': null
+ else if (projectId != null) 'project_id': projectId,
+ });
+ return Note.fromJson(response.data as Map);
+ } on DioException catch (e) {
+ throw dioToApp(e);
+ }
+ }
+
+ Future delete(int id) async {
+ try {
+ await _dio.delete('/api/notes/$id');
+ } on DioException catch (e) {
+ throw dioToApp(e);
+ }
+ }
+}
+```
+
+- [ ] **Step 5: Update `lib/data/repositories/notes_repository.dart`**
+
+```dart
+import '../api/notes_api.dart';
+import '../models/note.dart';
+
+class NotesRepository {
+ final NotesApi _api;
+ const NotesRepository(this._api);
+
+ Future> getAll() => _api.getAll();
+ Future getOne(int id) => _api.getOne(id);
+
+ Future create(
+ String title,
+ String body, {
+ List tags = const [],
+ int? projectId,
+ String noteType = 'note',
+ }) =>
+ _api.create(title, body,
+ tags: tags, projectId: projectId, noteType: noteType);
+
+ Future update(
+ int id,
+ String title,
+ String body, {
+ List tags = const [],
+ int? projectId,
+ bool clearProject = false,
+ String noteType = 'note',
+ }) =>
+ _api.update(id, title, body,
+ tags: tags,
+ projectId: projectId,
+ clearProject: clearProject,
+ noteType: noteType);
+
+ Future delete(int id) => _api.delete(id);
+}
+```
+
+- [ ] **Step 6: Update `lib/providers/notes_provider.dart`**
+
+```dart
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+
+import '../data/models/note.dart';
+import 'api_client_provider.dart';
+
+final notesProvider =
+ AsyncNotifierProvider>(NotesNotifier.new);
+
+class NotesNotifier extends AsyncNotifier> {
+ @override
+ Future> build() async {
+ return ref.watch(notesRepositoryProvider).getAll();
+ }
+
+ Future create(
+ String title,
+ String body, {
+ List tags = const [],
+ int? projectId,
+ String noteType = 'note',
+ }) async {
+ final note = await ref.read(notesRepositoryProvider).create(
+ title, body,
+ tags: tags,
+ projectId: projectId,
+ noteType: noteType,
+ );
+ state = AsyncData([...state.value ?? [], note]);
+ return note;
+ }
+
+ Future updateNote(
+ int id,
+ String title,
+ String body, {
+ List tags = const [],
+ int? projectId,
+ bool clearProject = false,
+ String noteType = 'note',
+ }) async {
+ final updated = await ref.read(notesRepositoryProvider).update(
+ id, title, body,
+ tags: tags,
+ projectId: projectId,
+ clearProject: clearProject,
+ noteType: noteType,
+ );
+ state = AsyncData([
+ for (final n in state.value ?? [])
+ if (n.id == id) updated else n,
+ ]);
+ return updated;
+ }
+
+ Future delete(int id) async {
+ await ref.read(notesRepositoryProvider).delete(id);
+ state = AsyncData([
+ for (final n in state.value ?? [])
+ if (n.id != id) n,
+ ]);
+ }
+}
+
+final noteDetailProvider =
+ FutureProvider.family((ref, id) async {
+ return ref.watch(notesRepositoryProvider).getOne(id);
+});
+```
+
+- [ ] **Step 7: Analyze**
+
+```bash
+flutter analyze lib/data/models/note.dart lib/data/api/notes_api.dart lib/data/repositories/notes_repository.dart lib/providers/notes_provider.dart
+```
+
+Expected: `No issues found!`
+
+- [ ] **Step 8: Commit**
+
+```bash
+git add lib/data/models/note.dart lib/data/api/notes_api.dart lib/data/repositories/notes_repository.dart lib/providers/notes_provider.dart test/widget_test.dart
+git commit -m "feat(knowledge): add noteType field to Note model and thread through API/repo/provider"
+```
+
+---
+
+## Task 3: KnowledgeItem Model
+
+**Files:**
+- Create: `lib/data/models/knowledge_item.dart`
+
+- [ ] **Step 1: Create the model**
+
+Create `lib/data/models/knowledge_item.dart`:
+
+```dart
+/// Unified model for all knowledge types returned by /api/knowledge.
+/// noteType is one of: 'note' | 'person' | 'place' | 'list' | 'task'
+class KnowledgeItem {
+ final int id;
+ final String noteType;
+ final String title;
+ final String body;
+ final List tags;
+ final int? projectId;
+ final int? milestoneId;
+ final int? parentId;
+ // Task-only fields — null for non-task types
+ final String? status;
+ final String? priority;
+ final String? dueDate;
+ final DateTime createdAt;
+ final DateTime updatedAt;
+
+ const KnowledgeItem({
+ required this.id,
+ required this.noteType,
+ required this.title,
+ required this.body,
+ required this.tags,
+ this.projectId,
+ this.milestoneId,
+ this.parentId,
+ this.status,
+ this.priority,
+ this.dueDate,
+ required this.createdAt,
+ required this.updatedAt,
+ });
+
+ factory KnowledgeItem.fromJson(Map json) => KnowledgeItem(
+ id: json['id'] as int,
+ noteType: json['note_type'] as String? ?? 'note',
+ title: json['title'] as String? ?? '',
+ body: json['body'] as String? ?? '',
+ tags: (json['tags'] as List?)
+ ?.map((e) => e as String)
+ .toList() ??
+ [],
+ projectId: json['project_id'] as int?,
+ milestoneId: json['milestone_id'] as int?,
+ parentId: json['parent_id'] as int?,
+ status: json['status'] as String?,
+ priority: json['priority'] as String?,
+ dueDate: json['due_date'] as String?,
+ createdAt: DateTime.parse(json['created_at'] as String),
+ updatedAt: DateTime.parse(json['updated_at'] as String),
+ );
+}
+```
+
+- [ ] **Step 2: Add a unit test to `test/widget_test.dart`**
+
+Append this group to the existing `main()` in `test/widget_test.dart`:
+
+```dart
+// Add this import at the top of the file:
+// import 'package:fabled_app/data/models/knowledge_item.dart';
+
+ group('KnowledgeItem.fromJson', () {
+ test('parses a task item with task fields', () {
+ final json = {
+ 'id': 10,
+ 'note_type': 'task',
+ 'title': 'Do the thing',
+ 'body': '',
+ 'tags': [],
+ 'status': 'todo',
+ 'priority': 'high',
+ 'due_date': '2025-01-01',
+ 'created_at': '2024-01-01T00:00:00',
+ 'updated_at': '2024-01-01T00:00:00',
+ };
+ final item = KnowledgeItem.fromJson(json);
+ expect(item.noteType, equals('task'));
+ expect(item.status, equals('todo'));
+ expect(item.priority, equals('high'));
+ });
+
+ test('defaults noteType to note when absent', () {
+ final json = {
+ 'id': 11,
+ 'title': 'A note',
+ 'body': '',
+ 'tags': [],
+ 'created_at': '2024-01-01T00:00:00',
+ 'updated_at': '2024-01-01T00:00:00',
+ };
+ final item = KnowledgeItem.fromJson(json);
+ expect(item.noteType, equals('note'));
+ expect(item.status, isNull);
+ });
+ });
+```
+
+Full updated `test/widget_test.dart`:
+
+```dart
+import 'package:fabled_app/data/models/knowledge_item.dart';
+import 'package:fabled_app/data/models/note.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ group('Note.fromJson', () {
+ test('parses noteType when present', () {
+ final json = {
+ 'id': 1,
+ 'title': 'Alice',
+ 'body': '',
+ 'tags': [],
+ 'note_type': 'person',
+ 'created_at': '2024-01-01T00:00:00',
+ 'updated_at': '2024-01-01T00:00:00',
+ };
+ final note = Note.fromJson(json);
+ expect(note.noteType, equals('person'));
+ });
+
+ test('defaults noteType to note when absent', () {
+ final json = {
+ 'id': 2,
+ 'title': 'My note',
+ 'body': '',
+ 'tags': [],
+ 'created_at': '2024-01-01T00:00:00',
+ 'updated_at': '2024-01-01T00:00:00',
+ };
+ final note = Note.fromJson(json);
+ expect(note.noteType, equals('note'));
+ });
+ });
+
+ group('KnowledgeItem.fromJson', () {
+ test('parses a task item with task fields', () {
+ final json = {
+ 'id': 10,
+ 'note_type': 'task',
+ 'title': 'Do the thing',
+ 'body': '',
+ 'tags': [],
+ 'status': 'todo',
+ 'priority': 'high',
+ 'due_date': '2025-01-01',
+ 'created_at': '2024-01-01T00:00:00',
+ 'updated_at': '2024-01-01T00:00:00',
+ };
+ final item = KnowledgeItem.fromJson(json);
+ expect(item.noteType, equals('task'));
+ expect(item.status, equals('todo'));
+ expect(item.priority, equals('high'));
+ });
+
+ test('defaults noteType to note when absent', () {
+ final json = {
+ 'id': 11,
+ 'title': 'A note',
+ 'body': '',
+ 'tags': [],
+ 'created_at': '2024-01-01T00:00:00',
+ 'updated_at': '2024-01-01T00:00:00',
+ };
+ final item = KnowledgeItem.fromJson(json);
+ expect(item.noteType, equals('note'));
+ expect(item.status, isNull);
+ });
+ });
+}
+```
+
+- [ ] **Step 3: Run tests**
+
+```bash
+flutter test test/widget_test.dart
+```
+
+Expected: `All tests passed!`
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add lib/data/models/knowledge_item.dart test/widget_test.dart
+git commit -m "feat(knowledge): add KnowledgeItem model with unit tests"
+```
+
+---
+
+## Task 4: KnowledgeApi + KnowledgeRepository + Provider Wiring
+
+**Files:**
+- Create: `lib/data/api/knowledge_api.dart`
+- Create: `lib/data/repositories/knowledge_repository.dart`
+- Modify: `lib/providers/api_client_provider.dart`
+
+- [ ] **Step 1: Create `lib/data/api/knowledge_api.dart`**
+
+```dart
+import 'package:dio/dio.dart';
+
+import '../models/knowledge_item.dart';
+import 'api_client.dart';
+
+class KnowledgeApi {
+ final Dio _dio;
+ const KnowledgeApi(this._dio);
+
+ /// Fetch a page of IDs matching the given filters.
+ /// Returns (ids, total).
+ Future<(List, int)> fetchIds({
+ String? noteType,
+ List tags = const [],
+ String sort = 'modified',
+ String? q,
+ int limit = 50,
+ int offset = 0,
+ }) async {
+ try {
+ final params = {
+ 'limit': limit,
+ 'offset': offset,
+ 'sort': sort,
+ if (noteType != null) 'type': noteType,
+ if (tags.isNotEmpty) 'tags': tags.join(','),
+ if (q != null && q.isNotEmpty) 'q': q,
+ };
+ final response = await _dio.get(
+ '/api/knowledge/ids',
+ queryParameters: params,
+ );
+ final data = response.data as Map;
+ final ids =
+ (data['ids'] as List).map((e) => e as int).toList();
+ final total = data['total'] as int;
+ return (ids, total);
+ } on DioException catch (e) {
+ throw dioToApp(e);
+ }
+ }
+
+ /// Batch-hydrate up to 100 IDs into full KnowledgeItems.
+ Future> fetchBatch(List ids) async {
+ if (ids.isEmpty) return [];
+ try {
+ final response = await _dio.get(
+ '/api/knowledge/batch',
+ queryParameters: {'ids': ids.join(',')},
+ );
+ final data = response.data as Map;
+ return (data['items'] as List)
+ .map((e) => KnowledgeItem.fromJson(e as Map))
+ .toList();
+ } on DioException catch (e) {
+ throw dioToApp(e);
+ }
+ }
+
+ /// Per-type counts for tab labels.
+ Future