Add invitation system, table of contents, actionable dashboard, and settings improvements
Invitation system (Phase 5.7): - invitation_tokens table (migration 0012) with SHA256-hashed tokens, 7-day expiry - Admin CRUD endpoints: POST/GET/DELETE /api/admin/invitations - Branded invitation email with registration link - /register-invite frontend view with token validation and account creation - Admin UI: invite form + pending invitations table with revoke - Configurable base URL setting for email links (replaces hardcoded Config.BASE_URL) Table of contents + markdown improvements (Phase 5.8): - TableOfContents component: sticky sidebar, heading parsing, smooth-scroll - Custom marked renderer adds id attributes to headings for anchor links - stripFirstLineTags() prevents leading #tags from rendering as headings - NoteViewerView/TaskViewerView flex layout with TOC sidebar (hidden ≤1200px) - Model catalog refresh: added llama3.2/3.3, gemma3, qwen3, phi4, deepseek-r1, qwen2.5-coder, dolphin3; added Reasoning category; removed discontinued models - Settings model section split into Installed/Available tabs Actionable dashboard (Phase 5.9): - due_before/due_after query params on /api/tasks (exclusive < / inclusive >=) - HomeView rewritten: overdue (red accent), due today, in progress, chats, notes - 5 parallel API calls via Promise.allSettled - Client-side done filtering and in-progress deduplication - Task sections hidden when empty; status toggle removes done tasks Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+69
-18
@@ -12,7 +12,7 @@
|
||||
> Include file-level details in the commit body when the change is non-trivial.
|
||||
|
||||
## Last Updated
|
||||
2026-02-13 — Phase 5.6: Assist Background-Task + Buffer Architecture
|
||||
2026-02-13 — Phase 5.9: Invitation system, table of contents, actionable dashboard, settings improvements
|
||||
|
||||
## Project Overview
|
||||
Fabled Assistant is a self-hosted note-taking and task-tracking application with
|
||||
@@ -190,6 +190,15 @@ for AI-assisted features.
|
||||
- Indexes: `category`, `user_id`, `created_at`, composite `(category, created_at DESC)`
|
||||
- Automatic retention cleanup via hourly asyncio task (configurable `LOG_RETENTION_DAYS`, default 90)
|
||||
|
||||
### Invitation Tokens
|
||||
- `id` (int PK), `email` (text NOT NULL), `token_hash` (text NOT NULL UNIQUE),
|
||||
`invited_by` (int FK users CASCADE), `expires_at` (timestamptz NOT NULL),
|
||||
`used` (boolean NOT NULL DEFAULT FALSE), `created_at` (timestamptz)
|
||||
- Admin creates invitation → raw token emailed → recipient registers via `/register-invite?token=...`
|
||||
- Token stored as SHA256 hash (same pattern as password reset)
|
||||
- 7-day expiration, one-time use, previous unused invitations for same email auto-revoked
|
||||
- Indexes: `token_hash`, `email`
|
||||
|
||||
### Messages
|
||||
- `id` (int PK), `conversation_id` (FK to conversations, CASCADE), `role` (str: system/user/assistant),
|
||||
`content` (str), `status` (str, default `'complete'` — `complete`/`generating`/`error`),
|
||||
@@ -219,7 +228,9 @@ fabledassistant/
|
||||
│ ├── 0007_add_title_and_updated_at_indexes.py # B-tree indexes on notes.title and conversations.updated_at
|
||||
│ ├── 0008_add_users_and_user_id.py # Users table, user_id FKs on notes/conversations/settings, composite PK for settings
|
||||
│ ├── 0009_add_message_status.py # Add status column to messages table (default 'complete')
|
||||
│ └── 0010_add_app_logs_table.py # App logs table for audit, usage, and error logging
|
||||
│ ├── 0010_add_app_logs_table.py # App logs table for audit, usage, and error logging
|
||||
│ ├── 0011_add_password_reset_tokens.py # Password reset tokens table
|
||||
│ └── 0012_add_invitation_tokens.py # Invitation tokens table
|
||||
├── src/
|
||||
│ └── fabledassistant/
|
||||
│ ├── __init__.py
|
||||
@@ -232,18 +243,19 @@ fabledassistant/
|
||||
│ │ ├── note.py # Note model (unified: id, title, body, tags[], parent_id, user_id, status, priority, due_date, timestamps)
|
||||
│ │ ├── conversation.py # Conversation + Message models with user_id
|
||||
│ │ ├── setting.py # Setting model (composite PK: user_id + key, value TEXT)
|
||||
│ │ └── app_log.py # AppLog model (id, category, user_id, username, action, endpoint, method, status_code, duration_ms, ip_address, details, created_at)
|
||||
│ │ ├── app_log.py # AppLog model (id, category, user_id, username, action, endpoint, method, status_code, duration_ms, ip_address, details, created_at)
|
||||
│ │ └── invitation.py # InvitationToken model (id, email, token_hash, invited_by, expires_at, used, created_at)
|
||||
│ ├── routes/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── api.py # /api blueprint with /health endpoint (public)
|
||||
│ │ ├── auth.py # /api/auth blueprint: register, login, logout, me, status
|
||||
│ │ ├── admin.py # /api/admin blueprint: backup, restore, user management, registration toggle (admin only)
|
||||
│ │ ├── auth.py # /api/auth blueprint: register, login, logout, me, status, password reset, invitation registration
|
||||
│ │ ├── admin.py # /api/admin blueprint: backup, restore, user management, registration toggle, invitations, base URL, SMTP (admin only)
|
||||
│ │ ├── chat.py # /api/chat blueprint: conversations CRUD, SSE message streaming (all @login_required)
|
||||
│ │ ├── notes.py # /api/notes CRUD + wikilinks + backlinks (all @login_required)
|
||||
│ │ ├── tasks.py # /api/tasks CRUD + PATCH status (all @login_required)
|
||||
│ │ └── settings.py # /api/settings GET/PUT — per-user settings (@login_required)
|
||||
│ ├── services/
|
||||
│ │ ├── auth.py # Auth business logic: hash_password, verify_password, create_user, authenticate, get_user_by_id, is_registration_open, list_users, delete_user, set_registration_open
|
||||
│ │ ├── auth.py # Auth business logic: hash_password, verify_password, create_user, authenticate, get_user_by_id, is_registration_open, list_users, delete_user, set_registration_open, password reset tokens, invitation tokens (create, validate, register_with_invitation, list_pending, revoke)
|
||||
│ │ ├── backup.py # Backup/restore: export_full_backup, export_user_backup, restore_full_backup
|
||||
│ │ ├── notes.py # CRUD with user_id isolation, is_task filter, convert, backlinks, search_notes_for_context
|
||||
│ │ ├── llm.py # Ollama interaction: build_context with user_id, streaming, URL fetching
|
||||
@@ -299,16 +311,17 @@ fabledassistant/
|
||||
│ ├── views/
|
||||
│ │ ├── LoginView.vue # Login form with error display, link to register
|
||||
│ │ ├── RegisterView.vue # Register form with password confirmation; shows "closed" message when registration disabled
|
||||
│ │ ├── UserManagementView.vue # Admin user management: registration toggle, user list with delete
|
||||
│ │ ├── RegisterInviteView.vue # Invitation-based registration: validates token, creates account with pre-set email
|
||||
│ │ ├── UserManagementView.vue # Admin user management: registration toggle, invitations (send/revoke), user list with delete
|
||||
│ │ ├── ChatView.vue # Dedicated /chat page: responsive sidebar (overlay on mobile), bubble messages, note picker, context pills
|
||||
│ │ ├── HomeView.vue # Landing page: recent notes + tasks + recent chats
|
||||
│ │ ├── HomeView.vue # Actionable dashboard: overdue tasks, due today, in progress, recent chats, recently edited notes
|
||||
│ │ ├── SettingsView.vue # Settings page: assistant name, model catalog, data export/restore (admin)
|
||||
│ │ ├── NotesListView.vue # Note list: search, sort, tag filter pills, pagination
|
||||
│ │ ├── NoteEditorView.vue # Create/edit: Tiptap editor, sticky toolbar, AI Assist panel (bottom 1/3), Ctrl+S, unsaved guard
|
||||
│ │ ├── NoteViewerView.vue # Markdown render, wikilink auto-create, convert-to-task, backlinks
|
||||
│ │ ├── NoteViewerView.vue # Markdown render, wikilink auto-create, convert-to-task, backlinks, table of contents sidebar
|
||||
│ │ ├── TasksListView.vue # Task list: search, status/priority filters, sort, pagination
|
||||
│ │ ├── TaskEditorView.vue # Create/edit task: Tiptap editor, sticky toolbar, AI Assist panel, Ctrl+S, dirty guard
|
||||
│ │ └── TaskViewerView.vue # Task detail: rendered markdown, badges, convert-to-note, backlinks
|
||||
│ │ └── TaskViewerView.vue # Task detail: rendered markdown, badges, convert-to-note, backlinks, table of contents sidebar
|
||||
│ ├── components/
|
||||
│ │ ├── LogsView.vue # Admin log viewer: stats summary, category/search/date filters, paginated table with expandable detail rows
|
||||
│ │ ├── AppHeader.vue # Nav bar: brand, nav links (incl. admin Logs), status indicator, theme toggle, user info + logout, hamburger menu (mobile)
|
||||
@@ -324,9 +337,10 @@ fabledassistant/
|
||||
│ │ ├── SearchBar.vue # Debounced search input
|
||||
│ │ ├── TagPill.vue # Clickable/dismissible tag pill
|
||||
│ │ ├── PaginationBar.vue # Prev/next + page numbers
|
||||
│ │ ├── TableOfContents.vue # Sticky sidebar TOC: parses markdown headings, smooth-scroll on click, hidden ≤1200px
|
||||
│ │ └── ToastNotification.vue # Fixed-position toast container with close button, warning support
|
||||
│ └── router/
|
||||
│ └── index.ts # Routes: /, /login, /register, /notes/*, /tasks/*, /chat, /chat/:id, /settings, /admin/users, /admin/logs; beforeEach auth guard
|
||||
│ └── index.ts # Routes: /, /login, /register, /register-invite, /notes/*, /tasks/*, /chat, /chat/:id, /settings, /admin/users, /admin/logs; beforeEach auth guard
|
||||
└── public/
|
||||
```
|
||||
|
||||
@@ -362,7 +376,14 @@ fabledassistant/
|
||||
| GET | `/api/notes/:id/backlinks` | List notes/tasks that reference this note via wikilinks |
|
||||
| POST | `/api/notes/assist` | Launch background assist generation, return 202 (body: `{body, target_section, instruction}`; 409 if already running) |
|
||||
| GET | `/api/notes/assist/stream` | SSE endpoint tailing assist generation buffer; supports `Last-Event-ID` reconnection; emits `chunk`, `done`, `error` events with 15s keepalives |
|
||||
| GET | `/api/tasks` | List tasks (params: `q`, `tag`, `status`, `priority`, `sort`, `order`, `limit`, `offset`) — queries notes where `status IS NOT NULL` |
|
||||
| GET | `/api/auth/invitation/:token` | Validate invitation token, returns `{valid, email?}` (public) |
|
||||
| POST | `/api/auth/register-with-invite` | Register with invitation token (body: `{token, username, password}`) (public) |
|
||||
| GET | `/api/admin/base-url` | Get application base URL setting (admin only) |
|
||||
| PUT | `/api/admin/base-url` | Set application base URL (admin only, body: `{base_url}`) |
|
||||
| POST | `/api/admin/invitations` | Create invitation (admin only, body: `{email}`) — sends email with registration link |
|
||||
| GET | `/api/admin/invitations` | List pending invitations (admin only) |
|
||||
| DELETE | `/api/admin/invitations/:id` | Revoke invitation (admin only) |
|
||||
| GET | `/api/tasks` | List tasks (params: `q`, `tag`, `status`, `priority`, `due_before`, `due_after`, `sort`, `order`, `limit`, `offset`) — queries notes where `status IS NOT NULL` |
|
||||
| POST | `/api/tasks` | Create task (body: `{title, body, status?, priority?, due_date?}` — accepts `description` as fallback for `body`) |
|
||||
| GET | `/api/tasks/:id` | Get single task |
|
||||
| PUT | `/api/tasks/:id` | Update task (accepts `body` or `description`, prefers `body`) |
|
||||
@@ -397,7 +418,7 @@ container startup.
|
||||
|
||||
### Migration Chain
|
||||
```
|
||||
0001_create_notes_table.py → 0002_create_tasks_table.py → 0003_task_note_companion.py → 0004_merge_tasks_into_notes.py → 0005_add_chat_tables.py → 0006_add_settings_table.py → 0007_add_title_and_updated_at_indexes.py → 0008_add_users_and_user_id.py → 0009_add_message_status.py → 0010_add_app_logs_table.py
|
||||
0001_create_notes_table.py → 0002_create_tasks_table.py → 0003_task_note_companion.py → 0004_merge_tasks_into_notes.py → 0005_add_chat_tables.py → 0006_add_settings_table.py → 0007_add_title_and_updated_at_indexes.py → 0008_add_users_and_user_id.py → 0009_add_message_status.py → 0010_add_app_logs_table.py → 0011_add_password_reset_tokens.py → 0012_add_invitation_tokens.py
|
||||
```
|
||||
|
||||
### How Migrations Run
|
||||
@@ -665,6 +686,31 @@ When adding a new migration, follow these conventions:
|
||||
- [x] **Frontend two-step pattern:** `useAssist.ts` calls `apiPost()` then `apiSSEStream()` with named event mapping (`chunk`/`done`/`error`); tracks stream handle for cleanup
|
||||
- [x] **Fixes `NS_ERROR_NET_PARTIAL_TRANSFER`:** Keepalive pings prevent browser/Hypercorn from closing connection during gaps between LLM chunks
|
||||
|
||||
### Phase 5.7 — Invitation System ✓
|
||||
- [x] **Invitation tokens table:** `invitation_tokens` (migration 0012) — SHA256-hashed tokens, 7-day expiry, one-time use
|
||||
- [x] **Admin invitation management:** `POST/GET/DELETE /api/admin/invitations` for create/list/revoke
|
||||
- [x] **Invitation email:** Branded HTML email with accept link, sent via fire-and-forget asyncio task
|
||||
- [x] **Invitation registration flow:** `GET /api/auth/invitation/:token` validates, `POST /api/auth/register-with-invite` creates account
|
||||
- [x] **Frontend:** `/register-invite` view with token validation, email pre-fill, username/password form
|
||||
- [x] **Admin UI:** Invite User section in UserManagementView with email input, pending invitations table, revoke action
|
||||
- [x] **Base URL admin setting:** Configurable via Settings UI; used in invitation and password reset email links (replaces `Config.BASE_URL` hardcoded fallback)
|
||||
|
||||
### Phase 5.8 — Table of Contents + Markdown Improvements ✓
|
||||
- [x] **TableOfContents component:** Sticky sidebar on note/task viewer — parses markdown headings, slugified IDs, smooth-scroll click, hidden on screens ≤1200px
|
||||
- [x] **Heading IDs in rendered markdown:** Custom `marked` renderer adds `id` attributes to headings for TOC anchor links
|
||||
- [x] **First-line tag stripping:** `stripFirstLineTags()` removes leading `#tag` lines from markdown before rendering (prevents tags from rendering as headings)
|
||||
- [x] **Viewer layout:** NoteViewerView and TaskViewerView use flex layout with main content + TOC sidebar (max-width 1200px)
|
||||
- [x] **Model catalog refresh:** Updated to current models (llama3.2/3.3, gemma3, qwen3, phi4, deepseek-r1, qwen2.5-coder, dolphin3), added Reasoning category, removed discontinued models
|
||||
- [x] **Settings model tabs:** Split model section into "Installed" and "Available" tabs
|
||||
|
||||
### Phase 5.9 — Actionable "Today" Dashboard ✓
|
||||
- [x] **Due date filtering API:** Added `due_before` (exclusive `<`) and `due_after` (inclusive `>=`) params to `list_notes()` and `/api/tasks` endpoint
|
||||
- [x] **Dashboard rewrite:** HomeView now shows Overdue (red left-border accent), Due Today, In Progress, Recent Chats, Recently Edited sections
|
||||
- [x] **Parallel fetching:** 5 API calls via `Promise.allSettled` — notes, overdue tasks, due-today tasks, in-progress tasks, chats
|
||||
- [x] **Client-side filtering:** Filters out `done` tasks from overdue/due-today, deduplicates in-progress against other lists
|
||||
- [x] **Task sections hidden when empty:** Only shown if there are matching tasks
|
||||
- [x] **Status toggle:** Marking a task `done` removes it from all dashboard lists
|
||||
|
||||
### Future / Stretch
|
||||
- Tagging/labeling system with LLM-suggested tags
|
||||
- Calendar/timeline view for tasks
|
||||
@@ -680,7 +726,7 @@ When adding a new migration, follow these conventions:
|
||||
- To reset database: `docker compose down -v && docker compose up --build`
|
||||
|
||||
## Current Status
|
||||
**Phase:** Phase 5.6 complete. Assist Background-Task + Buffer Architecture.
|
||||
**Phase:** Phase 5.9 complete. Invitation system, table of contents, actionable dashboard, settings improvements.
|
||||
- Full note-taking and task-tracking CRUD with markdown, wikilinks, backlinks, tags
|
||||
- **Tiptap WYSIWYG editor** with inline formatting preview, markdown round-trip, paste handling
|
||||
- **Tag/wikilink autocomplete** via `@tiptap/suggestion` with heading disambiguation
|
||||
@@ -693,17 +739,22 @@ When adding a new migration, follow these conventions:
|
||||
- Context pills with promote/exclude controls; note picker in chat input
|
||||
- Save assistant messages as notes (LLM-titled, chat-tagged); summarize conversations as notes
|
||||
- Dedicated `/chat` page with responsive sidebar (overlay on mobile)
|
||||
- Settings page: assistant name, model catalog, password change, data export/restore (admin)
|
||||
- **Registration control**: auto-closes after first user, admin toggle, password confirmation
|
||||
- **Admin user management**: `/admin/users` with user list + delete
|
||||
- Settings page: assistant name, model catalog (installed/available tabs), password change, data export/restore (admin)
|
||||
- **Invitation system**: admin sends email invitations, recipients register via token-based `/register-invite` flow
|
||||
- **Registration control**: auto-closes after first user, admin toggle, invitation-based registration
|
||||
- **Admin user management**: `/admin/users` with user list, delete, invite, revoke
|
||||
- **Session cookie hardening**: HttpOnly, SameSite=Lax, optional Secure flag
|
||||
- **Actionable dashboard**: HomeView shows overdue/due-today/in-progress task sections (hidden when empty), recent chats, recently edited notes
|
||||
- **Table of contents**: Sticky sidebar on note/task viewers, auto-generated from markdown headings
|
||||
- **App-wide layout fix**: navbar always visible, all views fit within viewport
|
||||
- **Responsive design**: hamburger menu, mobile touch targets, responsive breakpoints
|
||||
- **Docker Swarm production stack** with secrets, network isolation, health checks, resource limits
|
||||
- **Application logging**: audit (security events), usage (API requests), error (unhandled exceptions)
|
||||
- **Admin log viewer**: `/admin/logs` with stats, filters, pagination, expandable detail rows
|
||||
- **Automatic log retention**: configurable via `LOG_RETENTION_DAYS` (default 90 days)
|
||||
- **SMTP email notifications**: security alerts (login/logout/failed login/password change), task due date reminders
|
||||
- **SMTP email notifications**: security alerts (login/logout/failed login/password change), task due date reminders, invitation emails
|
||||
- **Admin SMTP configuration**: Settings UI with test email, DB-stored config with env var fallbacks
|
||||
- **Admin base URL setting**: Configurable public URL used in email links
|
||||
- **Per-user notification preferences**: toggle task reminders and security alerts independently
|
||||
- **Password reset flow**: Email-based with SHA256-hashed tokens, 1-hour expiry
|
||||
- Dark/light theme with CSS custom properties and design tokens
|
||||
|
||||
Reference in New Issue
Block a user