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:
2026-02-14 08:46:51 -05:00
parent d354da5b51
commit e02b681e91
20 changed files with 1506 additions and 295 deletions
+69 -18
View File
@@ -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