Add Projects, Milestones, RAG auto-inject, push notifications, PWA, tag normalisation

## Projects & Milestones (Phases A + G)
- New models: Project, Milestone (Project → Milestone → Task hierarchy)
- notes table: project_id + milestone_id FKs; parent_id FK constraint activated
- Migrations: 0017 (projects), 0018 (push_subscriptions), 0019 (events), 0020 (milestones)
- Services: projects.py, milestones.py (CRUD + progress tracking)
- Routes: /api/projects + /api/projects/<id>/milestones
- LLM tools: create/list/get/update project; create/list milestone; project + milestone + parent_task params on note/task tools
- Frontend: ProjectListView (stacked milestone bars), ProjectView (milestone-grouped kanban), ProjectSelector, MilestoneSelector, NoteEditorView + TaskEditorView updated

## RAG Auto-injection (Phase B)
- Notes ≥0.60 cosine similarity auto-injected into system prompt (max 3, 800 chars each)
- excluded_note_ids param; ChatView "Auto-included" sidebar section

## Summarisation improvements (Phase C)
- Threshold 20→30, keep-recent 6→8, max_tokens 200→400
- Two-pass summarisation for histories >50 messages

## Browser push notifications (Phase E)
- PushSubscription model + migration; pywebpush dependency
- /api/push routes; VAPID config; fire-and-forget on generation complete
- Frontend: sw.js, push store, Settings toggle

## PWA manifest (Phase F)
- manifest.json, Apple meta tags, service worker registration in main.ts

## Tag normalisation
- All tags lowercased + deduplicated at backend (create_note/update_note) and frontend (TagInput sanitize)
- Note/Task types gain project_id + milestone_id fields; store signatures updated

## CalDAV
- Radicale embedded server reverted; back to user-configured external CalDAV

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 20:52:21 -05:00
parent 3d7be5888e
commit 012eb1d46b
52 changed files with 4319 additions and 62 deletions
+124 -2
View File
@@ -12,7 +12,7 @@
> Include file-level details in the commit body when the change is non-trivial.
## Last Updated
2026-03-01Quick-capture overhaul (dedicated classifier, research + update_note support); CalDAV todo tools removed; run_research_pipeline buf optional
2026-03-02Projects + Milestones hierarchy; RAG auto-injection; summarization improvements; browser push notifications; PWA manifest; Radicale CalDAV reverted to external; Flutter Android companion app updated
## Project Overview
Fabled Assistant is a self-hosted note-taking and task-tracking application with
@@ -757,6 +757,47 @@ When adding a new migration, follow these conventions:
pills in ToolCallCard with one-click apply via append-tag API.
LLM instructed to use the `tags` parameter, not embed `#tag` text in note body.
### Projects & Milestones (Phases A + G)
- **Project model** (`models/project.py`): id, user_id (FK CASCADE), title, description, goal, status ("active"/"completed"/"archived"), color, timestamps.
- **Milestone model** (`models/milestone.py`): id, user_id, project_id (FK CASCADE), title, description, status, order_index, timestamps. Hierarchy: **Project → Milestone → Task**.
- **`notes` table**: `project_id` FK (SET NULL) + `milestone_id` FK (SET NULL) columns. `parent_id` column now has a FK constraint to `notes.id` (SET NULL) to enable sub-tasks.
- **Migrations**: `0017_add_projects.py`, `0020_add_milestones.py` (milestone_id on notes).
- **Services**: `services/projects.py` (CRUD + `get_or_create_project` + `get_project_summary`); `services/milestones.py` (CRUD + `get_milestone_progress` + `get_project_milestone_summary`).
- **Routes**: `routes/projects.py` under `/api/projects`; `routes/milestones.py` under `/api/projects/<pid>/milestones`.
- **LLM tools**: `create_project`, `list_projects`, `get_project`, `update_project`; `create_milestone`, `list_milestones`; `create_note`/`create_task`/`update_note` accept optional `project` + `milestone` + `parent_task` params.
- **Frontend**: `ProjectListView.vue` (card grid, stacked milestone progress bars per card), `ProjectView.vue` (milestone-grouped kanban + inline milestone management), `ProjectSelector.vue` (dropdown used in NoteEditorView + TaskEditorView), `MilestoneSelector.vue` (used in TaskEditorView).
- **Types**: `frontend/src/types/note.ts` Note interface now includes `project_id: number | null` and `milestone_id: number | null`.
- `app.py` registers `projects_bp` and `milestones_bp`; router adds `/projects` and `/projects/:id`.
### RAG Auto-injection (Phase B)
- Notes scoring ≥0.60 cosine similarity are injected automatically into every chat system prompt (max 3, up to 800 chars each) under `--- Relevant Notes ---` heading.
- `semantic_search_notes()` accepts `threshold` param (default 0.45 for sidebar suggestions; 0.60 for auto-inject).
- `build_context()` constants: `RAG_AUTO_THRESHOLD=0.60`, `RAG_AUTO_LIMIT=3`, `RAG_AUTO_SNIPPET=800`.
- `context_meta` adds `auto_injected_notes` list + `auto_injected: bool` flag on each `auto_notes` item.
- `excluded_note_ids` param in `build_context()` + POST body lets users remove a note from auto-injection.
- ChatView sidebar: "Auto-included" section (green border) above "Suggested" and "In Context".
### Summarization Improvements (Phase C)
- `_HISTORY_SUMMARY_THRESHOLD` raised 20 → 30; `_HISTORY_KEEP_RECENT` raised 6 → 8; summary `max_tokens` raised 200 → 400.
- Improved summary prompt captures: note/task/project names, decisions, open questions, topic arc.
- Two-pass summarization for histories >50 messages (first half → summary_a, combined → final).
### Browser Push Notifications (Phase E)
- **`models/push_subscription.py`**: PushSubscription — endpoint, p256dh, auth, user_agent; UNIQUE(user_id, endpoint).
- **Migration**: `0018_add_push_subscriptions.py` (down_revision="0017").
- **`services/push.py`**: `vapid_enabled()`, `save_subscription()`, `delete_subscription()`, `send_push_notification()` (lazy-imports pywebpush, fire-and-forget, removes 410 Gone subs).
- **`routes/push.py`**: `GET /api/push/vapid-public-key`, `POST/DELETE /api/push/subscribe`.
- Config: `VAPID_PRIVATE_KEY`, `VAPID_PUBLIC_KEY`, `VAPID_CLAIMS_SUB` env vars.
- `generation_task.py`: fires push after `buf.state = COMPLETED` (guarded by `vapid_enabled()`).
- `pywebpush>=2.0` added to `pyproject.toml`.
- **Frontend**: `frontend/public/sw.js` (push + notificationclick handlers); `frontend/src/stores/push.ts` (isSupported, permission, isSubscribed, subscribe/unsubscribe); SettingsView "Push Notifications" toggle.
### PWA Manifest (Phase F)
- `frontend/public/manifest.json`: name, short_name, start_url, display: standalone, icons.
- `index.html`: `<link rel="manifest">`, `<meta name="theme-color">`, Apple mobile meta tags.
- `main.ts`: service worker registration.
- Installable on iOS (Share → Add to Home Screen) and Android. Push works on Android (iOS 16.4+ partial).
### Authentication & User Management
- Session cookie auth with bcrypt, first-user-is-admin, orphaned data claiming
- Per-user data isolation across all resources
@@ -813,7 +854,7 @@ When adding a new migration, follow these conventions:
- Security headers applied in `after_request`: `X-Content-Type-Options`, `X-Frame-Options: DENY`, `Referrer-Policy`, `Content-Security-Policy`
- In-memory sliding-window rate limiter on all auth endpoints (login, register, forgot/reset password); proxy-aware client IP with `TRUST_PROXY_HEADERS`
- SQLAlchemy async engine configured with `pool_pre_ping=True` (tests connections before use, discards stale ones after Postgres restart) and `pool_recycle=1800` (recycles idle connections every 30 min to prevent TCP/firewall staleness)
- Alembic migrations: 15 migrations, all raw SQL with idempotency guards (IF NOT EXISTS, DO $$ BEGIN...EXCEPTION)
- Alembic migrations: 20 migrations (00010020), all raw SQL with idempotency guards (IF NOT EXISTS, DO $$ BEGIN...EXCEPTION)
- Config from env vars + Docker secrets file support (`_read_secret`)
## Development Workflow
@@ -824,6 +865,83 @@ When adding a new migration, follow these conventions:
Quart serves them
- To reset database: `docker compose down -v && docker compose up --build`
## Android Companion App
A native Flutter app at `/home/bvandeusen/Nextcloud/Projects/fabled_app` (separate repository).
### Stack
- Flutter + Dart, Riverpod (state), GoRouter (navigation), Dio (HTTP), PersistCookieJar (session)
- SSE streaming for chat using `fetch` + `ReadableStream` bridge
- Self-update support via Forgejo release API (`update_provider.dart`)
### Architecture
- **Data layer**: `lib/data/models/`, `lib/data/api/`, `lib/data/repositories/`
- **State**: Riverpod `AsyncNotifierProvider` per resource in `lib/providers/`
- **Navigation**: GoRouter with `ShellRoute` for tabbed main shell; `_RouterNotifier` refreshes on auth change
### Models & API Coverage
| Resource | Model fields | API endpoints |
|----------|-------------|---------------|
| Note | id, title, body, tags, project_id, milestone_id, created_at, updated_at | GET/POST /api/notes, GET/PUT/DELETE /api/notes/:id |
| Task | id, title, body, status, priority, due_date, project_id, milestone_id, parent_id, created_at, updated_at | GET/POST /api/tasks, GET/PUT/DELETE /api/tasks/:id |
| Project | id, title, description, goal, status, color, created_at, updated_at | GET/POST /api/projects, GET/PATCH/DELETE /api/projects/:id |
| Conversation | standard chat fields | GET/POST conversations, SSE stream |
| QuickCapture | text → type+title+message | POST /api/quick-capture |
### Navigation Shell (4 tabs)
Notes · Tasks · Projects · Chat — bottom `NavigationBar` on phone; `NavigationRail` on tablet/landscape.
Quick Capture bar at the top of the shell (persists across all tabs); Settings accessible from top-right icon.
### Feature Status
| Feature | Status | Notes |
|---------|--------|-------|
| Notes CRUD | ✅ | tags chip input; project selector in editor |
| Tasks CRUD | ✅ | project selector in editor |
| Projects list | ✅ | active/archived sections; long-press status change; create dialog |
| Chat + SSE | ✅ | full streaming |
| Quick Capture | ✅ | offline queue with retry |
| Tags | ✅ (model + API) | Chip input in NoteEditScreen; typed as `List<String>` |
| Project assignment | ✅ | `ProjectSelector` dropdown in Note + Task editors |
| Milestones | ❌ deferred | Too granular for mobile; web UI handles it |
| Push notifications | ❌ incompatible | Backend uses browser VAPID; Flutter needs FCM/APNs — separate implementation required |
| CalDAV settings | ❌ intentional | Server-side config only; not exposed in mobile app |
### Key Files
```
lib/
app.dart # GoRouter + _Shell + _QuickCaptureBar
core/constants.dart # Routes.*
data/
models/note.dart # Note (tags, project_id, milestone_id)
models/task.dart # Task (project_id, milestone_id, parent_id)
models/project.dart # Project
api/notes_api.dart # create/update accept tags, project_id
api/tasks_api.dart # create accepts project_id
api/projects_api.dart # full CRUD
repositories/notes_repository.dart
repositories/tasks_repository.dart
repositories/projects_repository.dart
providers/
api_client_provider.dart # all API + repository providers
notes_provider.dart # NotesNotifier
tasks_provider.dart # TasksNotifier
projects_provider.dart # ProjectsNotifier
screens/
notes/note_edit_screen.dart # chip tag input + ProjectSelector
tasks/task_edit_screen.dart # ProjectSelector
projects/project_list_screen.dart
widgets/
project_selector.dart # reusable DropdownButtonFormField
```
### API Compatibility Notes
- `GET /api/projects/:id` returns a flat JSON object (not `{project: ...}` wrapper); includes `summary` field.
- `POST /api/projects` returns project dict directly (201).
- `PATCH /api/projects/:id` returns updated project dict.
- Task body field name is `body` (not `description`) — Flutter maps `description` → `body` on serialize.
## Backlog
- Calendar/timeline view for tasks
- Import/export (Markdown files, JSON)
@@ -833,3 +951,7 @@ When adding a new migration, follow these conventions:
notes via wikilinks (`[[Title]]`) and shared tags. Nodes = notes/tasks; edges = wikilink references or
tag co-occurrence. Clicking a node navigates to that note. Filter by tag or depth. Useful for discovering
clusters of related notes and orphaned notes with no connections.
- **Flutter push notifications:** Requires FCM/APNs integration (separate from web VAPID). Would need
a FCM server key in Config and a new notification pathway in `generation_task.py`.
- **Flutter milestone support:** Milestone grouping in project view deferred; could add as a filtered
task list view once the feature is well-established on web.