Commit Graph

84 Commits

Author SHA1 Message Date
bvandeusen 6e9ec26c8e UI polish: notes/tasks list views
- NotesListView: skeleton shimmer for grid and list modes; grid columns change from fixed 3 to auto-fill(260px)
- NoteCard compact: flat border-bottom row style instead of stacked mini-cards; gradient fade mask on grid preview text
- TaskCard compact: flat border-bottom row style; show up to 2 tags
- TasksListView: gradient + shadow on New Task button (matches NotesListView)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 22:39:05 -04:00
bvandeusen f755067cbd feat: inline Logs panel into Settings
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 22:26:52 -04:00
bvandeusen c2565fb818 feat: inline Users panel into Settings
Ports UserManagementView logic (users list, invitations, registration
toggle) directly into SettingsView as a lazy-loaded 'users' admin tab.
Adds loadLogsPanel stub for Task 4. Adds apiDelete import and User type
import at script top.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 22:22:18 -04:00
bvandeusen 7c6d15179d feat: settings sidebar layout + card header style
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 21:54:00 -04:00
bvandeusen a6ce287be3 Dashboard: Mission Control layout — hero project, project grid, inbox
- Hero card: last-active project (by most recent note/task updated_at),
  prominent 'Open Workspace' button, milestone bars, 5 recent items,
  highest-priority next-up todo task
- Projects grid: all other active projects with small workspace button,
  urgency badges (in progress / todo count), milestone bars, hover lift
- Inbox section: collapsible strip of orphaned tasks + notes (no project)
- Skeleton shimmer replaces plain 'Loading...' text
- Chat widget + quick actions unchanged at top

Backend additions used: ?no_project=true on both /api/notes and
/api/tasks; ?project_id= on /api/tasks (committed separately)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 20:16:55 -04:00
bvandeusen 404aba1461 Task UX: forward-only advance button, auto edit mode, reload on save
TaskViewerView:
- Add '→ In Progress' / '→ Done' advance button in toolbar (forward-only,
  hidden when status is already done)
- Styled with primary indigo gradient to distinguish from secondary actions

TaskEditorView:
- showPreview now defaults false; set to true after load only when body
  has content — empty tasks open directly in Write mode
- Save on an existing task now calls window.location.reload() so the
  preview/edit mode re-evaluates from the freshly persisted body

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 18:50:36 -04:00
bvandeusen 474ed1fe05 UI polish: hover lift, skeleton loaders, empty states, badge contrast, transitions
Card & row interactions:
- NoteCard, TaskCard, ProjectCard: translateY(-2px) lift on hover + extended transitions
- Kanban task cards (ProjectView), note rows: same lift treatment
- Task rows in flat/grouped list: soft indigo bg tint on hover
- Chat conversation items: left-border accent + bg tint on hover (was border-only)
- HomeView project mini-cards: translateY(-2px) lift on hover

Global feedback:
- Buttons: scale(0.97) on :active press (theme.css, :not(:disabled) scoped)
- TagPill: color → primary + indigo bg tint on hover
- StatusBadge: increased opacity 15% → 22% + stronger text color for readability

Loading & empty states:
- TasksListView: shimmer skeleton loader (6 rows) + rich empty state with CTA
- ProjectListView: shimmer skeleton cards (4) + rich empty state with CTA
- HomeView: section-empty messages for overdue/high-priority/notes sections
- ProjectView kanban columns: dashed-border styled empty state

Transitions & graph:
- WorkspaceView panels: panel-fade opacity transition on show/hide (v-if inner wrap)
- GraphView D3 nodes: grow to r*1.3 + bold label on mouseover (150ms transition)
- Milestone action buttons: base opacity 0 → 0.35 for discoverability

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 18:34:52 -04:00
bvandeusen 3ef0e9f2c5 Delete empty untitled chat on navigate-away from ChatView
Previously the empty-conversation cleanup only fired when switching
between chats (convId watcher). Navigating to /notes, /tasks, etc.
left orphaned untitled empty conversations. onUnmounted now runs the
same check, deleting the current conversation if message_count === 0.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 18:02:57 -04:00
bvandeusen ffe1087aac Sync WorkspaceView queued bubble to indigo gradient (design language)
Matches the ChatView.vue update from the UI polish pass — queued bubbles
use linear-gradient(135deg, #6366f1, #4f46e5) and 18px border-radius
consistent with the Illuminated Transcript design language.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 17:33:54 -04:00
Bryan Van Deusen 3c3dd7377a UI polish: Illuminated Transcript design language
- Fraunces serif for headings and assistant identity label
- Chat bubbles flipped: assistant responses elevated (card + left indigo
  accent + shadow), user prompts ghosted (transparent bg, muted text)
- AppLogo: book fill changed from text-color to indigo in light mode
- AppHeader: border-bottom removed; active nav uses underline accent
- NoteCard/TaskCard: border replaced with whisper indigo-tint shadow
- HomeView: dashboard-response uses left accent; progress bars indigo;
  empty-state decorative marker; project cards use shadow depth
- NotesListView: btn-new/btn-cta use indigo gradient + glow
- All primary CTA buttons: indigo gradient (135deg #6366f1 → #4f46e5)
  with box-shadow glow on hover
- Dark palette: navy (#1a1a2e family) → slate-indigo (#111113 family)
- Scrollbars: thin 4px indigo-tinted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 15:18:22 -04:00
Bryan Van Deusen 5d9085d564 UI: flip bubble hierarchy — responses elevated, prompts recessed
Design direction: "Illuminated Transcript" — the AI response is the
primary artifact; user prompts are subordinate margin notes.

ChatMessage.vue:
- User bubble: ghosted background (rgba 3-4% opacity), muted text,
  thin near-invisible border. De-emphasized, recessed.
- Assistant bubble: left indigo accent border (2px), elevated shadow
  via --color-bubble-asst-shadow. Commands attention.
- Assistant role label: indigo (var(--color-primary)); user label muted.
- Removed old white-override content rules (no longer on dark bg).
- Thinking block: full border → left-only indigo accent bar (35% opacity),
  removed inner top border.

ChatView.vue:
- Streaming bubble: same left accent + shadow as assistant bubbles.
- Streaming role label: indigo to match ChatMessage.
- Thinking block in streaming: same treatment as ChatMessage.

theme.css:
- Added --color-bubble-user-bg/border/text for light + dark.
- Added --color-bubble-asst-shadow for light + dark.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 15:02:39 -04:00
Bryan Van Deusen 4a3899d103 UI: indigo/slate theme, border cleanup, rounder edges
theme.css:
- Dark mode palette shifted from navy to dark slate with indigo warmth
  (#111113 / #18181f / #1e1e27). Primary restored to true indigo
  (#818cf8) instead of the blue (#5b9cf6) it had drifted to.
- Borders reduced to near-invisible rgba(99,102,241,0.10) indigo tint.
- --radius-md 8px→12px, --radius-lg 12px→18px.
- Tag/wikilink/status-in-progress colors unified to indigo family.
- Thin indigo-tinted scrollbars (4px, rgba thumb).

ChatMessage.vue:
- Message bubbles 16px→18px radius.
- User bubble: flat primary → indigo gradient (#6366f1→#4f46e5).
- Assistant bubble: border removed, relies on bg-card vs bg contrast.

ChatView.vue:
- Sidebar border-right, chat-header border-bottom, context-sidebar
  border-left all removed.
- Streaming bubble: border removed, radius 16px→18px.
- Active conv item: solid primary fill → 15% opacity indigo tint with
  primary-colored text (softer, more refined selection state).
- Send button: flat primary → indigo gradient + glow on hover.
- New Chat button: glow on hover.
- Queued bubble: matches send button gradient.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 14:38:19 -04:00
bvandeusen da28a2f5c4 Fix queued bubble alignment and shape to match user messages
The role-user justify-content and message-bubble shape styles are scoped
to ChatMessage.vue and don't reach the inline queued bubbles. Add them
directly in ChatView and WorkspaceView: right-aligned row, 16px border
radius with 4px bottom-right corner, same padding and font as user bubbles.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 08:30:04 -04:00
bvandeusen 4dd3c1fe81 Fix queued bubbles, queue persistence, duplicate confirm UX, semantic check
- Queued message bubbles now match user bubble style (primary colour,
  right-aligned, opacity 0.45) in both ChatView and WorkspaceView
- Queue persisted to localStorage (fa_conv_queue_<id>); survives page
  refresh, restored on fetchConversation, cleared on delete
- ToolCallCard confirm/deny: buttons now inline in header row; "Create
  anyway" calls POST /api/notes or /api/tasks directly (no LLM round-trip);
  shows "✓ Created" / "Skipped" state; removed chatStore dependency
- POST /api/notes and POST /api/tasks now accept project (name string) and
  resolve to project_id server-side, matching tools.py behaviour
- Remove semantic similarity duplicate check from create_note and
  create_task — 0.87 threshold was too aggressive for topically-related
  notes; title-based exact/fuzzy checks are sufficient

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 08:11:39 -04:00
bvandeusen d7e1fe6aab UX: queue bubbles, dashboard queuing, duplicate confirm buttons, short-note fix
- Dashboard chat: allow typing/sending during streaming (queued in store)
  placeholder updates to "Type to queue…" during generation
- ChatView/WorkspaceView: replace queued chip with actual pending message
  bubbles — light grey, dashed border, "Queued" badge above content; clear
  button below the stack
- ToolCallCard: when tool returns requires_confirmation=True, show
  "Similar content found" label (not "Error"), link to the similar note,
  and "Create Anyway" / "Don't Create" buttons that auto-send the reply
- tools.py: fuzzy title match now returns requires_confirmation=True with
  similar_note data instead of a hard error, so numbered-series notes
  (Lore: X 0, Lore: X 1) can be created with one button click; semantic
  match responses also include similar_note for the link

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 23:32:21 -04:00
bvandeusen 12644999c1 Settings: reorganise into tabs (General, Account, Notifications, Integrations, Data, Admin)
Replaces the single long scrolling page with a tab bar. Active tab
persists to localStorage across navigation. Admin tab only visible to
admins. Push notifications description clarified to mention HTTPS requirement.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 23:20:33 -04:00
bvandeusen 059a2e06d5 Queue concurrent prompts instead of dropping or conflicting
Chat store: when sendMessage() is called while streaming, push to a
per-conversation queue and return. When the stream ends the next queued
message fires automatically via setTimeout(0) to keep the call stack
clean. clearQueue() and queuedCount exposed for UI consumption.
Queue is cleaned up on conversation delete.

ChatView/WorkspaceView: remove the streaming guard from the local
sendMessage() functions and the :disabled on the textarea so users can
type and submit freely while streaming. Input placeholder changes to
"Type to queue next message…" during streaming. A small " N queued ×"
chip appears below the streaming bubble showing queue depth with a
cancel button.

DashboardChatInput: disable input, attach button, and send button
during streaming. The dashboard creates a fresh conversation per
message so in-conversation queuing doesn't apply — locking the input
is the correct UX here. Placeholder updates to "Generating response…"
during streaming.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:05:49 -04:00
bvandeusen c65aad6639 Upgrade all major frontend dependencies
- TipTap 2 → 3: Extension from @tiptap/core, Placeholder from
  @tiptap/extensions, TaskList/TaskItem from @tiptap/extension-list,
  link: false in StarterKit (now bundles Link), @tiptap/core added
- marked 15 → 17: heading renderer updated to tokens/parseInline API
- Pinia 2 → 3, Vue Router 4 → 5 (no code changes required)
- Vite 6 → 7, @vitejs/plugin-vue 5 → 6, vue-tsc 2 → 3
- TypeScript 5.6 → 5.9: fixed Uint8Array<ArrayBuffer> strictness in
  push.ts, removed unused bodyEl ref in NoteViewerView.vue
- .npmrc: legacy-peer-deps=true for TipTap v3 peer dep resolution

TipTap 3 new capabilities now available: static renderer
(createStaticRenderer), MarkViews, @tiptap/extensions package.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 21:30:55 -04:00
bvandeusen ef141f07f8 UI polish pass: word count, slash commands, task lists, graph peek, bulk delete, export
- WordCount component (toggle words/chars vs read time, persisted mode)
- TipTap: TaskList/TaskItem extensions, slash command menu (H1-H3, lists, code, quote, task)
- Markdown serializer: task list → `- [ ]` / `- [x]` roundtrip
- GraphView: slide-in peek panel for note/task nodes (body, tags, linked nodes); tag nodes still navigate
- ChatView: bulk-select conversations with two-click confirm delete + chat retention policy (default 90d)
- NoteEditorView: pill tab bar with animated edit/preview toggle + WordCount in toolbar
- WorkspaceNoteEditor: inline search with tag matching, inline new-note creation, WordCount
- WorkspaceTaskPanel: task body rendered in slide-over + Edit link
- Settings: data export (Markdown ZIP / JSON) via GET /api/export
- Accessibility: skip-to-content link, aria-labels on all icon-only buttons
- Fix: export route used non-existent fabledassistant.database — corrected to async_session()
- Fix: ToolCallRecord.status type now includes "running" (was causing TS build error)
- Dockerfile: upgrade npm to latest before install to suppress major-version notice
- npm audit fix: patched minimatch (ReDoS) and rollup (path traversal) — 0 vulnerabilities

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 23:15:01 -05:00
bvandeusen de7709039a Persist workspace panel open/closed state per project
Panel state is saved to localStorage as workspace_panels_{projectId} on
every toggle and restored on mount. Each project remembers its own layout
independently. Fallback guard ensures at least one panel is always open
after restore.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 20:47:35 -05:00
bvandeusen fe63a732df Group conversations by date in chat sidebar
Conversations are bucketed into: Today, Yesterday, This week, This month,
and older entries sub-grouped by calendar month (e.g. "February 2026").
Group labels are sticky so they stay visible while scrolling. Older
buckets use month+year sub-grouping rather than a single "Older" catch-all.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 20:45:57 -05:00
bvandeusen 659c08def5 Keyboard navigation improvements across all editors
TiptapEditor.vue (central — applies to all three editors):
- Escape inside editor blurs it and emits 'escape' event to parent

TagInput.vue (central — applies everywhere tags are used):
- ArrowUp/ArrowDown navigate autocomplete suggestion list with visual highlight
- Enter confirms the keyboard-selected suggestion instead of typed text

NoteEditorView, TaskEditorView, WorkspaceNoteEditor (per-editor wiring):
- @escape on TiptapEditor returns focus to the title input (ref="titleRef")
- Ctrl+E from title input or editor main column jumps focus into editor body
- Ctrl+S on title input saves (was already on editor area; now consistent)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 18:46:08 -05:00
bvandeusen 320e879788 Workspace polish: milestone selector, streaming fix, push fix, log quieting
- WorkspaceTaskPanel: add milestone <select> in task detail (PATCH /api/notes/:id),
  replace delete ✕ with trash can SVG icon
- WorkspaceView: scroll to bottom when streaming ends so final message is visible
  without a page refresh
- ToolCallCard: fix search_notes result count (was reading data.total; tool returns
  data.count), so results no longer show "0 found"
- push.py: switch from deprecated WebPusher().send(vapid_private_key=...) to
  webpush() function (pywebpush 2.x API compatibility)
- app.py: downgrade /api/health, /api/chat/status, and static asset requests from
  INFO to DEBUG in after_request logger to reduce log noise at default log level

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 12:58:09 -05:00
bvandeusen 74ebb8a87f Project Workspace view, abort button, session invalidation, workspace fixes
Workspace (/workspace/:projectId):
- Three-panel layout (tasks / chat / notes) with CSS grid collapse toggles
- WorkspaceTaskPanel: tasks grouped by milestone, collapsible groups, task
  detail slide-over with status cycling, TaskLogSection work log, and
  inline-confirm delete
- WorkspaceNoteEditor: list view (sorted by updated_at, inline tag pills,
  inline-confirm delete) with editor view (TipTap, TagInput, Suggest tags,
  60s autosave)
- Persistent workspace conversation stored in localStorage per project;
  reused on return visits
- Thinking enabled (think: true) with Reasoning block in streaming bubble
- workspace_project_id backend pipeline: chat.py → generation_task.py →
  llm.py; system prompt uses project title so agent passes project="Title"
  to tools (fixes create_note failing with numeric project string)
- SSE tool-call watcher bridges agent actions to panel updates
- Height fix: workspace-root uses height 100%; app-content switches to
  overflow hidden via :has() selector
- Entry point: "Open Workspace" button on ProjectView

Abort button:
- Stop button in ChatView header and WorkspaceView input bar
- Calls existing cancelGeneration() / POST .../generation/cancel

Session invalidation:
- POST /api/auth/invalidate-sessions bumps session_version, keeps current
  session alive; useful after SSO/OAuth password rotation
- Button in Settings → Active Sessions section

Other:
- Dashboard recent notes limit increased from 8 to 16
- Workspace chat abort replaces Send button while streaming

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 11:34:06 -05:00
bvandeusen a8bb687349 Force-directed graph view with tag nodes, project clustering, physics tuning
- New /graph route with D3 force simulation (GraphView.vue)
- Tag nodes as first-class graph nodes (string IDs "tag:name") — clicking
  navigates to /notes?tags=name; tags shown by default
- Invisible project hub nodes attract project members into clusters
- Physics panel with live sliders: repulsion, link distance, link strength,
  project pull, gravity (forceX/forceY, not forceCenter)
- Wikilink edges retain directed arrowheads; tag edges are thin, no arrowhead
- Graph nav link in AppHeader; `g` shortcut in App.vue
- Backend: build_note_graph() emits tag nodes + note→tag edges instead of
  O(n²) note→note shared-tag mesh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 20:50:43 -05:00
bvandeusen 16ecd6bbeb DRY refactoring pass: shared mixins, route helpers, composables, CSS
Backend:
- models/base.py: TimestampMixin + CreatedAtMixin; applied to all 10+ models
- routes/utils.py: not_found() + parse_iso_date() helpers; used across all route files
- routes/milestones.py: _milestone_dict() helper replaces 5 repeated to_dict + progress blocks

Frontend:
- 5 new composables: useAutoSave, useEditorGuards, useTagSuggestions,
  useFloatingAssist, useListKeyboardNavigation
- ConfirmDialog.vue: reusable confirm modal replacing inline <teleport> blocks
- editor-shared.css: sidebar CSS consolidated from both editor views
- viewer-shared.css: context-bar/breadcrumb CSS consolidated from both viewer views
- NoteEditorView + TaskEditorView: ~120 lines each replaced with composable calls;
  duplicate scoped sidebar CSS removed
- NotesListView + TasksListView: inline keyboard-nav replaced with composable
- NoteViewerView + TaskViewerView: duplicate context-bar CSS removed

No behaviour changes. Net: -634 lines, +237 lines across 31 files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 15:26:34 -05:00
bvandeusen 48f070f773 Project-aware assist, link suggestions, project-scoped RAG, semantic search tool, SSE race fix
- Writing assistant: inject project notes as context (definition-tagged first), wikilink suggestions
- Link suggestions: server-side endpoint finds unlinked term occurrences, NoteEditorView sidebar panel
- Project-scoped RAG: ChatView ProjectSelector filters semantic+keyword search to selected project
- Semantic search tool: LLM search_notes upgraded to hybrid semantic (0.40 threshold) + keyword merge
- SSE race condition fix: drain remaining events after stream loop exits in chat.py and notes.py
- RAG_AUTO_SNIPPET raised 800→4000; sidebar include uses full note body; MAX_BODY_CHARS 8000→24000
- Enter-to-submit on writing assistant instruction textareas (note and task editors)
- DiffView: equal-line collapsing with 3-line context around changes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 14:02:54 -05:00
bvandeusen 9036dfd931 Note editor sidebar, full-doc assist, persistent drafts, version history
NoteEditorView: two-column sidebar layout (project/milestone/tags/assist
always visible), removed assist toggle button, InlineAssistPanel removed.

Writing assist: whole_doc mode rewrites entire document; DiffView.vue
replaces editor during review showing full-document diff. Scope dropdown
in sidebar switches between whole-document and section modes.

Persistent drafts: migration 0022 adds note_drafts (UNIQUE per note+user)
and note_versions (max 20, auto-pruned) tables. Draft saved after generation
completes, restored on editor mount, cleared on accept/reject. Version
snapshot created automatically whenever note body changes on save.

HistoryPanel.vue: version list + DiffView modal, restore button writes
body back to editor.

Config: OLLAMA_NUM_CTX default raised to 65536; assist num_predict now
tracks Config.OLLAMA_NUM_CTX instead of a hardcoded 4096.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 17:10:55 -05:00
bvandeusen 9bf047ec45 Task work log, inline writing assistant, task editor sidebar layout
Backend:
- Migration 0021: task_logs table (FK → notes + users, CASCADE, indexed)
- models/task_log.py: SQLAlchemy model with to_dict()
- services/task_logs.py: CRUD with ownership checks, _UNSET sentinel for optional duration clear
- routes/task_logs.py: GET/POST/PATCH/DELETE /api/tasks/<id>/logs
- services/tools.py: log_work LLM tool (resolves task by title, creates log entry)
- services/generation_task.py: retry assist generation up to 3× on HTTP 500

Frontend:
- types/task.ts: TaskLog interface
- TaskLogSection.vue: chronological work log with date+time timestamps, duration badge, inline edit, autofocus
- InlineAssistPanel.vue: streaming preview + diff review rendered inline in editor column
- useAssist.ts: removed chatStore.chatReady gate; toast notifications for errors
- NoteEditorView.vue + TaskEditorView.vue: inline assist panel, aside restricted to idle state
- TaskEditorView.vue: two-column layout (editor+log left, metadata sidebar right), body defaults to Preview, sidebarOpen accordion for mobile
- editor-shared.css: .assist-active-hint style

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 13:05:26 -05:00
bvandeusen dc39a56293 Per-conv streaming state, immediate message_count commit, auto-new-chat on open
stores/chat.ts:
- Replace 7 global stream refs with convStreams: Record<number, ConvStreamState>
- Each conversation tracks its own stream state; 7 names become computed getters
- Add isStreamingConv(id) public helper
- Increment message_count +2 immediately after POST 202 (not at done) so the
  cleanup watcher never deletes a mid-generation conversation
- SSE reconnect retries now run for background conversations regardless of
  which conv is currently viewed
- error toasts only shown when the erroring conv is currently viewed
- deleteConversation cleans up orphaned stream state

ChatView.vue:
- Remove invalid write to store.lastContextMeta (now a read-only computed)
- Add !store.isStreamingConv(newId) guard to watch(convId) fetch so navigating
  back to a generating conversation shows accumulated content without stale refetch
- Add startNewConversation() helper; opening /chat with no ID auto-creates a
  new conversation and redirects to it (on mount and on navigation)
- Deleting a conversation also auto-creates a new one

Also committing previous-session changes (keyboard nav + task routing):
- App.vue: e shortcut only on note-view (not task-view, which is removed)
- TaskCard.vue: route directly to /tasks/:id (viewer), remove View buttons
- router/index.ts: remove task-view route; /tasks/:id now maps to TaskEditorView
- TasksListView.vue: Enter key pushes to /tasks/:id

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 20:32:56 -05:00
bvandeusen 969ef0efa3 Add keyboard navigation: e/c/slash shortcuts, j/k list nav, focus visibility
- App.vue: e → edit on viewer pages; / → focus search (custom event); c → focus
  chat on home or navigate to /chat; update shortcuts panel with Lists + Chat sections
- theme.css: add a:focus-visible to focus-ring rules (router-link cards now show
  visible keyboard focus outline)
- SearchBar.vue: expose focus() via defineExpose for cross-component focus dispatch
- NotesListView / TasksListView: j/k vim-style navigation with kb-active-item
  highlight; listen for shortcut:focus-search; cleanup on unmount
- HomeView.vue: listen for shortcut:focus-chat, call chatInputRef.focus()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 08:39:49 -05:00
bvandeusen 3836f08007 Add overflow-x: clip and min-width: 0 to all list view containers
Prevents content from escaping max-width boundaries across notes,
tasks, projects list, and project detail views.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 22:13:58 -05:00
bvandeusen c8b3d0f4e1 Fix dashboard overflow: clip at container boundary, min-width: 0 on grid children
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 22:06:22 -05:00
bvandeusen 7fed77318d Fix notes grid columns: dashboard 2-per-row, notes list 3-per-row
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 21:58:31 -05:00
bvandeusen 5b07a3349b Dashboard: equal 50/50 columns, 2 project cards/row, 3 note cards/row
- grid-template-columns: 3fr 2fr → 1fr 1fr (equal split)
- projects-strip-inline: 3 columns → 2 columns
- notes-mini-grid: auto-fill minmax(220px) → repeat(3, 1fr)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 21:46:07 -05:00
bvandeusen e3873d7483 Route task card clicks to edit view; repurpose button as View
Clicking a task card now goes directly to the edit view since that's
the primary use case. The hover button (compact) and button (full card)
are now labeled "View" and link to the detail/viewer view instead.
Sub-task links in TaskViewerView also route to edit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 19:51:58 -05:00
bvandeusen ab0002f342 Move Active Projects widget into tasks column, 3 cards across
Projects now appear at the top of the tasks column directly above
the task sections, laid out in a fixed 3-column grid. The previous
full-width widget below the main grid has been removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 19:48:32 -05:00
bvandeusen eac4c6d86a Implement keyboard shortcuts and enrich note/task viewer views
Keyboard shortcuts (App.vue):
- g+h/n/t/p/c: navigate to home/notes/tasks/projects/chat
- n / t: new note / new task (when not in an input field)
- Escape: go home (when not typing)
- Shortcuts panel updated to document all working shortcuts

NoteViewerView:
- Context breadcrumb: parent note link (↑), project badge, milestone badge
- Fetches project and parent note titles in parallel on load
- Back button label improved to "← Notes"

TaskViewerView:
- Context breadcrumb: parent task link (uses parent_title from API), project badge, milestone badge
- Sub-tasks section with inline progress bar and status dots (clickable to advance)
- Sub-tasks loaded from /api/notes?parent_id=X&type=task
- Note type extended with optional parent_title field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 19:32:19 -05:00
bvandeusen 3bc6443161 Fix notes column overflow on wide viewports
Add min-width: 0 to .col-tasks and .col-notes so CSS grid items
shrink to their track size. Change notes-mini-grid from repeat(2, 1fr)
to auto-fill minmax(220px, 1fr) so cards wrap instead of overflowing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 19:15:39 -05:00
bvandeusen 56d5268fad Overhaul dashboard, notes, and tasks views for density and usability
NoteCard: add compact prop — single-row title/tags/timestamp, no body preview
TaskCard: add compact prop — single-row with status dot, priority badge,
  title, optional project breadcrumb, due date; edit button appears on hover.
  Add projectTitle prop for cross-view context.

NotesListView: auto-fill CSS grid layout (2–4 columns); compact list toggle;
  view mode persisted to localStorage.

TasksListView: compact rows throughout; group-by-project toggle with
  collapsible sections, task counts, and "Open project" links; fetches
  project list for group labels and task breadcrumbs; limit raised to 100
  in grouped mode; view mode persisted to localStorage.

HomeView dashboard: task sections use compact rows (project breadcrumb shown);
  notes column uses 2-per-row mini-grid; new "Active Projects" widget below
  main grid shows milestone progress bars for up to 6 active projects.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 18:57:57 -05:00
Bryan Van Deusen 87d3f3ea16 Fix project/milestone association and redesign AppHeader navigation
Backend:
- routes/tasks.py: POST + PUT were silently dropping project_id,
  milestone_id, parent_id from request body — root cause of association
  not saving from the task editor
- routes/tasks.py: GET /api/tasks/:id now includes parent_title in
  response (secondary lookup when parent_id is set)
- routes/notes.py: add PATCH /api/notes/:id for partial updates (used
  by sub-task status toggle; PUT already existed but PATCH was missing)
- routes/projects.py: GET /api/projects/:id/notes now fetches milestone
  IDs and passes them via milestone_ids so tasks assigned to a milestone
  (but lacking project_id) are included in the project view
- services/notes.py: create_note() auto-sets project_id from milestone
  when milestone_id is provided and project_id is omitted; list_notes()
  gains milestone_ids param — when combined with project_id uses OR
  condition (project_id=X OR milestone_id IN (...))

Frontend:
- NoteEditorView: add MilestoneSelector; milestone resets when project
  changes; all save paths (save/create/auto-save) include milestone_id
- stores/notes.ts: add milestone_id to createNote + updateNote types
- TaskEditorView: sub-tasks now inherit milestone_id from parent task
- AppHeader: three-zone layout — brand left, Notes/Projects/Tasks/Chat
  centered (absolute positioning), right rail with status/theme/? and
  gear dropdown containing Settings/Users/Logs; mobile dropdown unchanged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-03 13:34:01 -05:00
bvandeusen 4fd2c915b6 Improve project/task/sub-task workflows across backend and web
Backend:
- notes.py: add parent_id filter to list_notes()
- routes/notes.py: expose project_id, milestone_id, parent_id, type
  query params on GET /api/notes
- milestones.py: add find_milestone_by_title() (cross-project search)
- tools.py: list_notes project filter + updated_at; list_tasks milestone
  without project; tag_mode default add; fail-fast project resolution

Web frontend:
- TaskEditorView: add sub-tasks section (fetch, toggle, create inline);
  pre-fill projectId/milestoneId/parentId from URL query params on new task
- ProjectView: add + button in Todo kanban column header linking to
  /tasks/new?projectId=X&milestoneId=Y for quick task creation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 22:00:44 -05:00
bvandeusen 6580d16942 Improve tools reliability and add milestone management to ProjectView
tools.py:
- Default tag_mode changed from 'replace' to 'add' — existing tags are
  preserved unless the user explicitly requests replacement
- create_task, create_note, update_note, create_milestone: project and
  milestone lookup now uses get_by_title with fuzzy fallback; returns a
  clear error (with a hint to use list_projects/list_milestones) instead
  of silently creating duplicate projects or milestones via get_or_create
- create_task: project/milestone resolved before note creation so a bad
  project name fails fast without leaving an orphaned task behind
- update_note return value now includes item_type, tags, project_id, and
  an 'updated' summary string so the LLM can confirm what was modified
- research_topic stub now logs an error and returns failure instead of
  silently returning success when reached via wrong code path

ProjectView.vue:
- Milestone headers now show edit (✎) and delete (✕) action buttons on hover
- Edit triggers inline rename input; Enter/blur commits, Escape cancels
- Delete opens a confirmation modal clarifying tasks are unlinked not deleted

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 21:33:13 -05:00
bvandeusen 012eb1d46b 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>
2026-03-02 20:52:21 -05:00
bvandeusen 3d7be5888e Remove intent model entirely; quick-capture uses primary model
The separate intent model (OLLAMA_INTENT_MODEL / qwen2.5:7b) is removed
from every part of the system. All classification now uses the primary model.

Changes:
- config.py: remove OLLAMA_INTENT_MODEL
- intent.py: remove classify_intent() and all supporting infrastructure
  (_SYSTEM_PROMPT_TEMPLATE, _RESEARCH_PREFIX, _PRIOR_WORK_REFS); file now
  only contains the quick-capture classifier
- quick_capture.py: classify_capture_intent() now called with Config.OLLAMA_MODEL
- generation_task.py: remove intent_model_setting DB lookup and get_setting import;
  history summarization and research pipeline use the primary model directly
- research.py: remove intent_model parameter from run_research_pipeline() and
  _generate_sub_queries(); both use the model param throughout
- routes/settings.py: remove intent_model from model-key validation and response
- app.py: remove intent model pre-warming at startup
- SettingsView.vue: remove Intent Model selector and related refs/state

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 18:41:49 -05:00
bvandeusen 90afd3f131 Improve suggested notes: limit 8, threshold 0.45, show relevance scores
- Raise similarity threshold 0.30 → 0.45: only genuinely relevant notes
  shown; loosely-related notes no longer pad the sidebar
- Increase max suggested notes 3 → 8 (zero added compute — threshold is
  the real gate; the embedding call is fixed regardless of limit)
- semantic_search_notes now returns list[tuple[float, Note]] instead of
  list[Note] so scores propagate through context_meta to the frontend
- Keyword fallback notes carry score=null (no cosine similarity available)
- ChatView sidebar shows % badge on each suggested note:
  green ≥75%, amber 60–74%, muted <60%
  Hovering reveals the raw score in a tooltip

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 12:10:39 -05:00
bvandeusen 0af68ec86c Fix leftover attachedNote reference causing TS build error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 19:20:40 -05:00
bvandeusen 5c84c28a67 Make note picker add notes to persistent context instead of one-shot
Previously the paperclip button created a one-shot attachment that was
only included for the single message it was sent with, then discarded.
Now selecting a note via the picker calls includeNote() directly, so it
appears in the "In Context" sidebar and stays for the entire conversation
— consistent with clicking "+" on a suggested note.

Removed attachedNote ref, removeAttachedNote(), the pinned-note sidebar
block, and the contextNoteId/contextNoteTitle sendMessage arguments that
are no longer needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 18:53:41 -05:00
bvandeusen 590682a5d2 Phase 22: SearXNG web research pipeline + settings layout overhaul
Research pipeline (research_topic tool):
- New service: services/research.py — sub-query generation, SearXNG
  search, URL fetch, deduplication, and LLM synthesis into a note
- 5 sub-queries × 3 pages = up to 15 sources, capped at 12 for synthesis
- Synthesis uses num_ctx=16384 + max_tokens=8192 for long-form output
- Prompt demands 2500+ words, 6+ topic-appropriate sections, detailed prose
- 429 retry with backoff; 1s inter-query sleep; raw_decode JSON parsing

search_web tool (new):
- Lightweight single-query SearXNG search, results returned inline in chat
- LLM answers conversationally in round 1; no note created
- web_search result type with external links in ToolCallCard

Infrastructure:
- llm.py: generate_completion accepts num_ctx override
- config.py: SEARXNG_URL + Config.searxng_enabled()
- docker-compose: OLLAMA_NUM_PARALLEL=2, commented SEARXNG_URL example
- intent.py: search_web and research_topic routing rules

Settings UI:
- 2-column grid layout (small sections pair up, complex span full width)
- Search Test section: live SearXNG query with result preview
- GET /api/settings/search?q= proxy endpoint
- Research button (magnifier) in ChatView input toolbar → popover modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:21:38 -05:00
bvandeusen 432e0bd2a0 Show Qwen3 thinking output in chat as collapsible Reasoning block
Ollama streams message.thinking tokens alongside message.content when
think=True — previously silently dropped. Now forwarded end-to-end.

Backend:
- llm.py: ChatChunk type gains "thinking" variant; stream_chat_with_tools
  yields ChatChunk(type="thinking") for msg.thinking chunks before content
- generation_task.py: thinking chunks emit "thinking_chunk" SSE events
  (not added to content_so_far — not persisted to DB)

Frontend:
- types/chat.ts: Message.thinking?: string (session-only, not from DB)
- stores/chat.ts: streamingThinking ref; thinking_chunk handler accumulates
  chunks; on done, thinking carried into committed Message object then cleared
- ChatMessage.vue: collapsible <details class="thinking-block"> shown for
  messages that have .thinking content (collapsed by default)
- ChatView.vue + ChatPanel.vue: live thinking block in streaming bubble —
  open while only thinking is flowing, auto-collapses when content arrives;
  typing indicator hidden while thinking is active

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 23:16:59 -05:00