Add Tiptap inline-preview editor, layout improvements, and README

Replace plain textarea with Tiptap (ProseMirror) WYSIWYG editor for notes
and tasks. Headings, bold, lists, and code blocks now render inline while
editing. Tags and wikilinks get visual highlighting via decoration plugins,
and autocomplete uses @tiptap/suggestion with heading disambiguation.

Layout: pin navbar with app-shell flex column (100dvh), sticky editor
toolbar above scrolling content, AI Assist panel fixed at bottom 1/3 with
full-height section selector and prompt. Increase max-width to 960px
across all views. Hide assist controls during streaming/review.

Other fixes: markdown paste handling, auto-syncing assist sections via
body watcher, trailing newline on AI accept, ChatView height fix.

Add README.md describing the app, usage guide, and technical overview.
Update summary.md with Phase 5.2 roadmap and current status.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-11 21:24:35 -05:00
parent cbfdf5289e
commit 586026bff6
29 changed files with 2088 additions and 438 deletions
+38 -16
View File
@@ -12,7 +12,7 @@
> Include file-level details in the commit body when the change is non-trivial.
## Last Updated
2026-02-11 — Phase 5.1: Chat UX Improvements
2026-02-11 — Phase 5.2: Tiptap Inline-Preview Editor + Layout Improvements
## Project Overview
Fabled Assistant is a self-hosted note-taking and task-tracking application with
@@ -233,7 +233,7 @@ fabledassistant/
├── vite.config.ts
├── tsconfig.json
├── src/
│ ├── App.vue # Shell: AppHeader + router-view + ChatPanel (slide-out) + ToastNotification; starts status polling + loads settings on mount
│ ├── App.vue # Shell: .app-shell (100dvh flex column) with AppHeader + .app-content (flex:1, scrollable) wrapping router-view; starts status polling + loads settings on mount
│ ├── main.ts # App init, imports theme.css
│ ├── assets/
│ │ └── theme.css # CSS custom properties: light/dark themes, body reset, design tokens, responsive breakpoints (480/768/1024), utility classes (.hide-mobile/.hide-desktop), mobile touch targets
@@ -241,7 +241,8 @@ fabledassistant/
│ │ └── client.ts # ApiError class, apiGet/apiPost/apiPut/apiPatch/apiDelete + apiSSEStream (EventSource-based), auto 401→login redirect
│ ├── composables/
│ │ ├── useTheme.ts # Theme toggle, localStorage, prefers-color-scheme
│ │ ── useAutocomplete.ts # #tag + [[wikilink]] autocomplete: Tab cycling, debounced search
│ │ ── useAutocomplete.ts # Legacy textarea autocomplete (replaced by Tiptap suggestion extensions)
│ │ └── useAssist.ts # AI Assist composable: section parsing, target selection, LLM streaming, accept/reject; watches body ref for auto-sync
│ ├── stores/
│ │ ├── auth.ts # Auth state: user, isAuthenticated, isAdmin, login/register/logout/checkAuth
│ │ ├── notes.ts # CRUD + tag filter, resolveTitle, convertToTask, convertToNote, fetchBacklinks, fetchAllTags (with toast errors)
@@ -255,9 +256,16 @@ fabledassistant/
│ │ ├── chat.ts # Message, Conversation, ConversationDetail, ContextMeta, OllamaModel, OllamaStatus interfaces
│ │ ├── settings.ts # AppSettings interface, ModelInfo interface (name, description, size, bestFor, category)
│ │ └── task.ts # Task = re-export of Note; TaskListResponse
│ ├── extensions/
│ │ ├── TagDecoration.ts # ProseMirror decoration plugin: highlights #tags with .inline-tag class
│ │ ├── WikilinkDecoration.ts # ProseMirror decoration plugin: highlights [[wikilinks]] with .wikilink class
│ │ ├── TagSuggestion.ts # @tiptap/suggestion extension for #tag autocomplete (suppressed at line start for headings)
│ │ ├── WikilinkSuggestion.ts # @tiptap/suggestion extension for [[wikilink]] autocomplete
│ │ └── suggestionRenderer.ts # Shared Vue renderer for suggestion dropdowns (creates/positions SuggestionDropdown)
│ ├── utils/
│ │ ├── tags.ts # extractTags(), linkifyTags() (with (?<!&) to skip HTML entities), linkifyWikilinks()
│ │ ── markdown.ts # renderMarkdown() (full) + renderPreview() (strips links/images) — decodes HTML entities before marked, replaces &#39; after sanitization
│ │ ── markdown.ts # renderMarkdown() (full) + renderPreview() (strips links/images) — decodes HTML entities before marked, replaces &#39; after sanitization
│ │ └── markdownSerializer.ts # Tiptap JSON → markdown serializer: handles all StarterKit nodes + marks
│ ├── views/
│ │ ├── LoginView.vue # Login form with error display, link to register
│ │ ├── RegisterView.vue # Register form (username, password, email), first-user setup
@@ -265,10 +273,10 @@ fabledassistant/
│ │ ├── HomeView.vue # Landing page: recent notes + tasks + recent chats
│ │ ├── 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: Ctrl+S, unsaved guard, toasts, autocomplete
│ │ ├── 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
│ │ ├── TasksListView.vue # Task list: search, status/priority filters, sort, pagination
│ │ ├── TaskEditorView.vue # Create/edit task: Ctrl+S, dirty guard, autocomplete
│ │ ├── 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
│ ├── components/
│ │ ├── AppHeader.vue # Nav bar: brand, nav links, status indicator, theme toggle, user info + logout, hamburger menu (mobile)
@@ -278,7 +286,9 @@ fabledassistant/
│ │ ├── TaskCard.vue # Card with rendered preview (body not description), StatusBadge (clickable), PriorityBadge, due date, tags
│ │ ├── StatusBadge.vue # Color-coded status badge, optional clickable cycling
│ │ ├── PriorityBadge.vue # Color-coded priority indicator (hidden for "none")
│ │ ├── MarkdownToolbar.vue # Bold/italic/link/list/heading toolbar for editor
│ │ ├── MarkdownToolbar.vue # Tiptap command-based toolbar: bold/italic/link/list/heading with active state highlighting
│ │ ├── TiptapEditor.vue # Tiptap wrapper: markdown↔HTML round-trip, paste handling, selection change emit, expose editor
│ │ ├── SuggestionDropdown.vue # Shared autocomplete dropdown for tag/wikilink suggestions
│ │ ├── SearchBar.vue # Debounced search input
│ │ ├── TagPill.vue # Clickable/dismissible tag pill
│ │ ├── PaginationBar.vue # Prev/next + page numbers
@@ -558,6 +568,21 @@ When adding a new migration, follow these conventions:
- [x] **Save-as-note LLM titles:** `save_response_as_note` generates title via LLM (same pattern as chat titles); falls back to first-line extraction on failure
- [x] **Chat tag on saved notes:** Both `save_response_as_note` and `summarize_conversation_as_note` tag created notes with `chat`
### Phase 5.2 — Tiptap Inline-Preview Editor + Layout Improvements ✓
- [x] **Tiptap WYSIWYG editor:** Replaced plain textarea with Tiptap (ProseMirror-based) inline-preview editor — headings, bold, italic, lists, code blocks render inline while editing
- [x] **Markdown round-trip:** Load markdown → HTML (via `marked`) → Tiptap editing → markdown (via custom `serializeToMarkdown()`) on every change
- [x] **Tag/wikilink decorations:** ProseMirror decoration plugins visually highlight `#tag` and `[[wikilink]]` as colored badges without custom node types
- [x] **Autocomplete migration:** Moved from textarea-based `useAutocomplete` to `@tiptap/suggestion` framework with shared `SuggestionDropdown` component
- [x] **Heading vs tag disambiguation:** Tag suggestion suppressed at start of paragraphs (where `#` is likely a heading prefix)
- [x] **Markdown paste handling:** Pasted markdown text auto-detected and converted to formatted content
- [x] **Toolbar refactor:** `MarkdownToolbar` uses Tiptap commands with active state highlighting instead of textarea text insertion
- [x] **Sticky toolbar:** Toolbar, title, tabs pinned above scrolling editor content
- [x] **App shell layout:** Navbar always visible — `App.vue` uses flex column at 100dvh with header + content areas; all views fit below header
- [x] **AI Assist panel:** Fixed at bottom 1/3 of viewport; section selector and prompt fill the panel; controls hidden during streaming/review (only output + accept/reject shown)
- [x] **Auto-syncing sections:** `useAssist` watches `body` ref to keep section list in sync on load, typing, and AI accept
- [x] **Accept trailing newline:** Accepted AI suggestions always end with a newline for clean separation
- [x] **Wider content area:** Max-width increased from 720px to 960px across all views
### Future / Stretch
- Tagging/labeling system with LLM-suggested tags
- Calendar/timeline view for tasks
@@ -573,24 +598,21 @@ When adding a new migration, follow these conventions:
- To reset database: `docker compose down -v && docker compose up --build`
## Current Status
**Phase:** Phase 5.1 complete. Chat UX Improvements.
**Phase:** Phase 5.2 complete. Tiptap Inline-Preview Editor + Layout 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
- Unified note/task model (a task is a note with `status IS NOT NULL`)
- **Multi-user authentication** with session cookies, bcrypt passwords, admin role
- **Per-user data isolation** across notes, conversations, and settings
- **First-user-is-admin** pattern; orphaned pre-auth data claimed by first user
- LLM chat via Ollama with background generation task + SSE streaming
- **LLM-generated titles** on first exchange and every 10th message
- **Stop generation** button with partial content preservation
- **Relative timestamps** in sidebar (5m ago, 3h ago, then dates)
- **Empty chat cleanup** on navigation away
- **AI Assist panel** pinned to bottom 1/3 of editor viewport with auto-syncing sections
- Note-aware context: auto-includes current note + searches related notes by keyword
- 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, **data export/restore (admin)**
- **Request logging** with timing, sanitized 500 errors, configurable LOG_LEVEL
- Settings page: assistant name, model catalog, data export/restore (admin)
- **App-wide layout fix**: navbar always visible, all views fit within viewport
- **Responsive design**: hamburger menu, mobile touch targets, responsive breakpoints
- **Toast notifications** with success/error/warning types and dismiss button
- **Docker Swarm production stack** with secrets, network isolation, health checks, resource limits
- Dark/light theme with CSS custom properties and design tokens