Commit Graph
100 Commits
Author SHA1 Message Date
bvandeusenandClaude Sonnet 4.6 9383f10dab ci: build only on dev push and release tag, not on main merge
Merging to main now runs CI checks only. The release tag (v*) is the
sole trigger for a production :latest image, eliminating the redundant
double-build that previously occurred on every PR merge + tag pair.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 16:13:18 -04:00
bvandeusenandClaude Sonnet 4.6 4636c9a973 Add CalVer build-time version tracking
- Dockerfile: ARG BUILD_VERSION=dev → ENV APP_VERSION baked into image
- ci.yml: BUILD_VERSION passed as build-arg; set to git tag name on v*
  tag builds, "dev" on branch builds
- routes/api.py: GET /api/version returns {"version": APP_VERSION}
- SettingsView: fetches /api/version on mount, displays in About section
  under General tab

Version source of truth is the git tag (YY.MM.DD.N CalVer).
pyproject.toml / package.json versions are no longer maintained.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 16:10:57 -04:00
bvandeusenandClaude Sonnet 4.6 767fa73e1d Update summary.md and README for sharing/collaboration/backup session
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 15:40:25 -04:00
bvandeusenandClaude Sonnet 4.6 c7ef709633 Multi-user sharing, groups, and in-app notifications
Backend:
- Alembic migration 0025: groups, group_memberships, project_shares,
  note_shares, notifications tables
- models: Group, GroupMembership, ProjectShare, NoteShare, Notification
- services/access.py: permission resolution (viewer/editor/admin/owner)
- services/groups.py + routes/groups.py: full group CRUD + membership
- services/sharing.py + routes/shares.py: project/note sharing API
- services/notifications.py: in-app notification create/list/mark-read
- routes/in_app_notifications.py: GET/POST notification endpoints
- routes/users.py: user search endpoint
- services/projects.py + services/notes.py: *_for_user variants
- routes updated to use *_for_user on get/list; adds permission field
- app.py: register all new blueprints

Frontend:
- api/client.ts: ShareEntry, GroupEntry, NotificationEntry types + helpers
- stores/notifications.ts: Pinia store with polling
- NotificationBell.vue: bell icon + badge + dropdown toggle + 60s poll
- NotificationsPanel.vue: unread notification list with mark-all-read
- ShareDialog.vue: teleport modal for sharing projects/notes with users/groups
- SharedWithMeView.vue: /shared route listing shared projects and notes
- AppHeader: NotificationBell, Shared nav link
- ProjectView, NoteViewerView, TaskViewerView: Share button + ShareDialog
- SettingsView: Groups admin tab with create/delete groups + member mgmt
- router: /shared route

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 15:37:00 -04:00
bvandeusenandClaude Sonnet 4.6 878b149f4d feat(backup): v2 backup/restore with all models and FK re-mapping
Export now includes: projects, milestones, task_logs, note_drafts,
note_versions, push_subscriptions; full user fields (oauth_sub,
session_version); full message fields (status, tool_calls).

Restore v2 builds ID maps for all tables and patches FKs in dependency
order (users → projects → milestones → notes → child records). Notes
restored in two passes to handle self-referential parent_id correctly.
v1 backups still restore via _restore_v1 path (parent_id bug also fixed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 11:57:21 -04:00
bvandeusenandClaude Sonnet 4.6 de4ab53363 Add implementation plans: sharing/collaboration and backup rewrite
- docs/plans/2026-03-11-sharing-collaboration.md
  7 chunks / 12 tasks covering DB migration, models, access control service,
  groups CRUD, sharing service, notifications, and all frontend components

- docs/plans/2026-03-11-backup-rewrite.md
  4 tasks covering v2 export (full + user), v2 restore with FK re-mapping,
  and round-trip verification; v1 restore preserved

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 11:46:54 -04:00
bvandeusenandClaude Sonnet 4.6 5d3f8683a4 ProjectView UI overhaul: Fraunces title, gradient CTA, stat chips, kanban colors
- Gradient 'Open Workspace' CTA replacing flat button
- Fraunces serif project title input (1.75rem, borderless underline)
- Summary stat chips with colored status dots (todo/in-progress/done/notes)
- Kanban columns with 3px color-coded top border (muted/blue/green)
- Task cards with left-border priority indicator (high=red, med=amber, low=blue)
- Activity timestamp with clock icon via relativeTime composable
- Shimmer skeleton loader during project fetch
- Quieter danger-outline delete button
- Milestone chevron/edit/delete use SVG icons
- Notes tab uses relative timestamps + document icon
- Remove unused priorityClass/formatDate functions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 11:11:41 -04:00
bvandeusenandClaude Sonnet 4.6 8a83ba8e11 Task views: same UI refinement pass as note views
TaskEditorView:
- Back button standardized to "← Tasks"
- Toolbar now on same row as Write/Preview tabs (flex-direction row)
- Save button and title input inherit gradient/Fraunces from editor-shared.css

TaskViewerView:
- Shimmer skeleton loader replaces plain "Loading..." text
- Task title uses Fraunces font at 2rem
- Edit button becomes primary gradient CTA
- Meta timestamps show clock/pencil SVG icons
- Backlinks section: card grid with colored type badges (note=indigo, task=amber)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 11:00:08 -04:00
bvandeusenandClaude Sonnet 4.6 978a2627fb UI refinement: toolbar icons, gradient CTA, Fraunces title, viewer polish
MarkdownToolbar:
- Replace text labels with SVG icons (Material Design paths)
- Group buttons with visual separators: text / headings / lists / code / links
- Add Strikethrough and Blockquote buttons (previously missing)
- Active state: tinted bg + ring instead of solid fill; hover lift shadow
- Toolbar container: pill shape with border (matches rest of UI)

editor-shared.css:
- btn-save: gradient + glow (linear-gradient(135deg, #6366f1, #4f46e5))
- title-input: borderless with Fraunces font and focus underline

NoteEditorView:
- Back button label standardized to "← Notes" (was "Back")
- Write/Preview tabs and toolbar now share the same row (flex-direction row)

NoteViewerView:
- Shimmer skeleton loader replaces plain "Loading..." text
- Note title uses Fraunces font at 2rem
- Edit button becomes primary gradient CTA
- Meta line shows clock/pencil SVG icons alongside timestamps
- Backlinks section: card grid with colored type badges (note=indigo, task=amber)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 10:57:02 -04:00
bvandeusenandClaude Sonnet 4.6 810f63e749 Associate research_topic notes with workspace project
run_research_pipeline now accepts project_id; generation_task.py passes
workspace_project_id when the tool is called from a workspace context.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-11 09:29:56 -04:00
bvandeusenandClaude Sonnet 4.6 71301f631d Workspace UI overhaul: rail note panel, split task detail, project goal header
- WorkspaceNoteEditor: replace toggling list/editor views with always-visible
  left rail (155px) showing note list alongside editor pane; persist last-open
  note per project in localStorage (workspace_note_{projectId})
- WorkspaceTaskPanel: add priority dot + due date on task rows; replace full
  overlay detail with bottom-split (44% list / 56% detail); close button
  replaces back nav; active row highlighted; fade transition
- WorkspaceView: show project goal in header instead of 'Workspace'; non-equal
  panel widths (0.8fr / 1.1fr / 1.1fr); empty chat quick-action chips (Project
  status / New note / Add tasks); prefill() helper

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 23:51:17 -04:00
bvandeusenandClaude Sonnet 4.6 e4b2cd3aeb Tasks list: smart sections view (overdue / today / this week / upcoming / no date / done)
Replaces the flat sorted list with date-aware sections that surface what
needs attention first. Completed tasks go into a collapsed section at the
bottom. Grouped-by-project mode is preserved as the second toggle option.
TaskCard compact restored to card hover/lift style.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 23:12:07 -04:00
bvandeusenandClaude Sonnet 4.6 3b5e84052a Remove docs/superpowers and gitignore it
Planning artifacts (specs/plans) generated by Claude Code sessions don't belong
in the repo. Removing existing files and ignoring the directory going forward.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 22:40:44 -04:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 befee1110b feat: replace gear dropdown with direct settings link
Remove the gear dropdown menu from AppHeader; cog icon is now a plain
router-link to /settings. Admin-only /admin/users and /admin/logs routes
are replaced with redirects to /settings. Mobile menu admin links removed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 21:13:43 -04:00
bvandeusenandClaude Sonnet 4.6 89f3d94895 docs: settings rework implementation plan
Tasks: AppHeader/router cleanup, SettingsView sidebar layout,
inline Users and Logs panels.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 21:11:12 -04:00
bvandeusenandClaude Sonnet 4.6 1047298765 docs: settings rework design spec
Sidebar navigation with User/Admin groups, Option-A grouped card
content style, cog header direct link, Users and Logs panels inlined.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 20:43:04 -04:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 1c6d009603 API: no_project filter + project_id on tasks route
- list_notes(): add no_project param → Note.project_id.is_(None) filter
- GET /api/notes: expose ?no_project=true
- GET /api/tasks: add ?project_id= and ?no_project=true pass-through

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 20:04:03 -04:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 bb1f29c8e2 Header: increase height and logo size for better visibility
- Nav padding: 0.5rem → 0.75rem vertical, 1.25rem → 1.5rem horizontal
- AppLogo size: 24px → 34px so the book detail is legible

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 18:36:10 -04:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 16220c57a0 Fix user bubble text contrast, Chat nav active state, and update summary.md
- theme.css: darken user bubble text (#3a3a4a light / #b0b0c8 dark) for better readability
- AppHeader: highlight Chat nav link when on any /chat/* route (was only active on exact /chat)
- summary.md: document workspace, graph, queue persistence, ToolCallCard direct-API, dedup scoping

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 18:00:04 -04:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 f5bee4573f Scope duplicate detection to same type (notes vs tasks)
create_note now only checks against existing notes (is_task=False);
create_task only checks against existing tasks (is_task=True). All three
checks (exact title, fuzzy title, semantic similarity) pass the type
filter through to list_notes and semantic_search_notes.

Adds is_task param to semantic_search_notes() so callers can restrict
results to notes or tasks independently.

Prevents a note titled "Design enemy AI" from blocking a task with the
same title, and vice versa.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 08:35:54 -04:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 a90342c9cb Restore semantic duplicate check at 0.90 threshold, 200-char minimum
Raises body character limit from 80 → 200 (skips short/stub notes)
and threshold from 0.87 → 0.90 (only flags near-identical content).

Enemy design notes sharing a template will pass through cleanly;
accidentally duplicated notes with the same detailed content will
still trigger the confirmation prompt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-10 08:20:20 -04:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 4327b9f858 CI: use venv for Python tests to isolate setuptools from system Python
deadsnakes python3.12 sees system python3.10's setuptools at
/usr/lib/python3/dist-packages and skips installation, leaving python3.12
without setuptools. A venv gets its own site-packages so pip/setuptools
are properly present for http-ece's legacy setup.py build.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:47:08 -04:00
bvandeusenandClaude Sonnet 4.6 2b4163523b CI: pre-install http-ece with --no-build-isolation to fix PEP 517 isolation
pip builds legacy setup.py packages in an isolated environment that doesn't
inherit the globally installed setuptools. Pre-building http-ece with
--no-build-isolation bypasses that isolation so setuptools is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:34:12 -04:00
bvandeusenandClaude Sonnet 4.6 52d14672e5 CI: install setuptools before dev deps to support legacy http-ece build
http-ece (pulled in by pywebpush) uses setup.py and requires setuptools,
which is not included in the deadsnakes Python 3.12 install.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:29:05 -04:00
bvandeusenandClaude Sonnet 4.6 090df1633a Fix ruff E741: rename ambiguous variable l → log in task_logs route
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:28:34 -04:00
bvandeusenandClaude Sonnet 4.6 690270519f Fix push notifications: focus suppression, empty body, error visibility
- sw.js: suppress notification when the target chat tab is already focused
  (clients.matchAll visibility check before showNotification)
- generation_task.py: provide meaningful body for tool-only responses
  (lists tool names instead of sending an empty string that browsers discard);
  promote scheduling failure from debug to warning
- push.py: promote send errors from warning to error with exc_info;
  log successful sends at INFO so they're visible in normal operation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 22:12:20 -04:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 e34f6d6e54 Bump all GitHub Actions to latest major versions
actions/checkout v4 → v6
actions/setup-node v4 → v6  (Node 24 runtime, fixes punycode warning)
docker/setup-buildx-action v3 → v4  (Node 24 + ESM)
docker/login-action v3 → v4  (Node 24 + ESM)
docker/build-push-action v6 → v7  (Node 24 + ESM)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 21:41:14 -04:00
bvandeusenandClaude Sonnet 4.6 bb41ce48be Fix Python CI jobs: drop container, install Python 3.12 directly
python:3.12-slim has no Node, causing actions/checkout@v4's post-step
to fail with 'exec: node not found'. Drop the container: declaration
so jobs run on the ubuntu-latest runner (which has Node). Install
Python 3.12 via deadsnakes PPA for the test job since the runner base
image (Ubuntu 22.04) ships with Python 3.10.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 21:33:11 -04:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 8b96115bb2 Bump vue to 3.5.30 (patch)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 19:57:33 -04:00
bvandeusenandClaude Sonnet 4.6 c6211e5239 Gate Docker build on CI passing
Merged ci.yml and build.yml into a single workflow. The build job now
declares needs: [typecheck, lint, test] so images are only pushed when
all checks are green. PRs run CI only; branch/tag pushes run CI then
build if successful.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 18:53:58 -04:00
bvandeusenandClaude Sonnet 4.6 3d861d08eb Skip semantic duplicate check for bare-title tasks/notes
The semantic similarity check was flagging unrelated short-title tasks
as duplicates (e.g. "Lore: Shell 0" matching "Lore: Reinitialization 0"
at 91%) because with no body, the embedding is purely title-based and
co-domain tasks in the same project share a tight embedding neighborhood.

Only run the semantic check when the body is ≥ 80 chars — enough
content to make a meaningful comparison. The fuzzy title check already
covers exact/near-exact title duplicates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 18:49:25 -04:00
bvandeusenandClaude Sonnet 4.6 bd9ba9b526 Fix scanner probes returning 200 via SPA catch-all
The 404 handler was unconditionally serving index.html (200) for all
non-API, non-static paths, including scanner probes for .php, .asp, .cgi
etc. Added _SPA_EXTENSIONS set so paths with unknown extensions get a
real 404 instead of a misleading 200.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-09 18:49:25 -04:00
bvandeusenandClaude Sonnet 4.6 02df3a4be4 Add dev branch support to build workflow
dev push  → :dev + :<sha>
main push → :latest + :<sha>
tag push  → :latest + :<sha> + :<version>

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 21:17:53 -04:00
bvandeusenandClaude Sonnet 4.6 fbd5d04f6f Add version tag support to build workflow
Tag pushes (v*) now push an additional versioned image tag alongside
:latest and :<sha>. To release: git tag v1.2.0 && git push origin v1.2.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 21:04:50 -04:00
bvandeusenandClaude Sonnet 4.6 65c17ab7e8 Fix Python jobs: use python:3.12-slim container instead of setup-python
actions/setup-python fails in act_runner because it tries to download
pre-built binaries from GitHub CDN which aren't available in this
environment. Using a Python container image directly is the correct
approach — same pattern as the Flutter jobs in fabled_app.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:42:35 -04:00
bvandeusenandClaude Sonnet 4.6 ef3a4fafd4 Add path filters to CI and build workflows
Prevents workflows from running on changes to docs, README, infra,
summary.md, or other non-code files. Build workflow also skips on
test-only changes since tests don't affect the Docker image.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 20:39:12 -04:00
bvandeusenandClaude Sonnet 4.6 feac66dcd0 Scope CI/CD to build+test only; clean up runner infra
Remove automated deploy — Portainer handles deployments for now.
build.yml: deploy job removed, only builds and pushes image to registry.
infra/runner-compose.yml: clean standalone Portainer stack for act_runner,
  no swarm network dependency, uses external Forgejo URL.
infra/runner-swarm.yml: removed (replaced by runner-compose.yml).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 19:46:46 -04:00
bvandeusenandClaude Sonnet 4.6 93fcd753a6 Simplify CI/CD for same-swarm topology: no SSH, direct service update
Since Forgejo and the app share a swarm, the runner can:
- Connect to Forgejo at http://forgejo:3000 (internal, no Traefik)
- Call docker service update directly via mounted socket
- Avoid SSH entirely — secrets drop from 6 to 3

infra/runner-swarm.yml: runner added to Forgejo stack, pinned to manager
  node (required for service API access), uses Docker Swarm Config object
  for act_runner config injection
infra/act-runner-config.yml: runner config (bridge network for jobs,
  docker socket whitelisted)
build.yml: deploy step replaced with docker service update --with-registry-auth
  --detach=false (waits for rollout, shows progress in Actions log)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 19:06:11 -04:00
bvandeusenandClaude Sonnet 4.6 1dc771cec2 Fix CI/CD for Forgejo-on-swarm topology; add runner swarm compose
- build.yml: clarify that runner needs Docker socket; SSH deploy uses
  DEPLOY_PATH secret so app host path is configurable; --no-deps so
  db/ollama containers are left untouched during deploy
- infra/runner-swarm.yml: act_runner as a Docker service on the Forgejo
  host — mounts host socket for buildx, writes act_runner config via
  one-shot init container, connects to fabled_backend network

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 18:55:40 -04:00
bvandeusenandClaude Sonnet 4.6 e128406790 Add Forgejo Actions CI/CD, ruff linting, and test foundation
- .forgejo/workflows/ci.yml: fast checks on every push (TS typecheck,
  Python lint via ruff, pytest) — no Docker build, ~30s with cache
- .forgejo/workflows/build.yml: Docker build with registry-side layer
  caching + SSH deploy on main branch pushes only
- pyproject.toml: add ruff to dev deps, configure pytest and ruff rules
- tests/: conftest.py + first unit tests for _safe_filename (no DB needed)
- Makefile: add lint, fmt, typecheck, test, check targets for local use

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-08 18:46:54 -04:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 e107da911a Collapse tool call cards by default; expand on click
- Completed (success) tool calls render collapsed: label + inline summary + ▶ chevron
- Clicking the header expands the detail section (result list, links, tags)
- Error cards start expanded so failures are immediately visible
- Running (in-progress) cards start expanded for live progress visibility
- Auto-collapses when a running call completes during streaming
- Suggested-tag pills remain accessible in both collapsed (via separate row) and expanded states

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 20:42:40 -05:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 0e9ab84afb Workspace note editor: toolbar, link suggestions, wikilink button, Ctrl+S
- Add MarkdownToolbar to the workspace note editor (was missing entirely)
- Add [[ button to MarkdownToolbar so wikilinks are discoverable in all editors;
  clicking inserts [[ which immediately triggers the WikilinkSuggestion dropdown
- Add link suggestions strip: polls /api/notes/link-suggestions 2.5s after edit,
  shows unlinked note-title mentions as clickable chips to wrap in [[...]], plus
  "All" button to apply everything at once — directly addresses the heading→wikilink
  workflow (note title appearing as heading text gets detected and offered for linking)
- Add Ctrl+S keyboard shortcut on title input and editor area to save
- Replace ✕ delete icon on note list items with trash can SVG (consistency with task panel)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-07 18:26:30 -05:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 b33aa25fb7 Session invalidation on credential change
Add session_version to users table. Sessions now carry session_version
alongside user_id; @login_required rejects any session where the version
doesn't match the DB value.

- migration 0024: session_version INTEGER NOT NULL DEFAULT 1
- models/user.py: session_version Mapped[int] column
- auth.py: version mismatch → session.clear() + 401
- services/auth.py: change_password and reset_password_with_token both
  increment session_version, evicting all other live sessions
- routes/auth.py: login/register/oauth_callback store session_version;
  update_password rehydrates current session with new version so the
  user who changed their password stays logged in

Existing sessions will require one re-login after the migration runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-06 16:30:14 -05:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 b11a92f32d Fix writing assist: disable thinking mode, drop stuck-buffer 409
- stream_chat: add think=False parameter passed through to Ollama payload.
  qwen3 models have thinking enabled by default; without this flag the model
  spends minutes generating internal thinking tokens that stream_chat silently
  discards, leaving the frontend spinner blank until the SSE connection times
  out and the widget disappears.

- create_assist_buffer: orphan (overwrite) a still-running buffer instead of
  raising. The old asyncio task holds a direct reference and completes
  harmlessly against the stale buffer. New requests always win.

- assist_route: remove the 409 guard that blocked new requests when a previous
  generation got stuck. create_assist_buffer now handles this transparently.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-05 13:28:59 -05:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 404d58d037 Update summary.md with keyboard navigation changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 08:41:42 -05:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 264c3664ba Escape progressively unfocuses then navigates home
First Escape blurs the active input (reaching neutral state where
shortcuts work), second Escape navigates home. Allows full keyboard
navigation without touching the mouse to leave a focused field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-04 08:20:26 -05:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 47c9cb39a2 Auto-generate VAPID keys at startup; no manual configuration needed
On first boot, ensure_vapid_keys() generates a fresh EC P-256 key pair
and saves it to /data/vapid_keys.json (inside the app_data volume) so
it survives container restarts. Subsequent boots load from that file.

VAPID_PRIVATE_KEY / VAPID_PUBLIC_KEY env vars still take precedence for
deployments that prefer to manage keys externally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 23:07:30 -05:00
bvandeusenandClaude Sonnet 4.6 7d532b82a6 Fix startup warm-up to use user model settings instead of env var
Startup no longer auto-warms Config.OLLAMA_MODEL. Instead it queries
all distinct default_model values from user settings, cross-references
with Ollama's installed models, and warms only the intersection.

Models that users have selected but not yet installed are skipped with
an info log — they are never auto-pulled. The embedding model pull
behaviour is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 22:58:20 -05:00
bvandeusenandClaude Sonnet 4.6 226ee5b22f Change default OLLAMA_MODEL from llama3.1 to qwen3:14b
Updates the startup warm-up and fallback default to match the
currently preferred model. Instances without a user setting will
also default to qwen3:14b.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 22:51:42 -05:00
bvandeusenandClaude Sonnet 4.6 76c384ea53 Fix chat model always using conv.model instead of user setting
conv.model was stored at conversation creation time and short-circuited
the get_setting() call, so changing default_model in Settings had no
effect on existing conversations. Now the user's default_model setting
is always the source of truth for generation and summarization.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 22:44:14 -05:00
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 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
bvandeusenandClaude Sonnet 4.6 53e54ea761 Remove intent router from chat pipeline; raise OLLAMA_NUM_CTX to 16384
The intent classifier (Phase 21) is removed from the main chat generation
path. The main model now handles all tool routing natively via Ollama's
structured tool-calling API, eliminating misidentification issues caused
by the small intent model.

Changes:
- generation_task.py: remove classify_intent call, intent_task, _WRITE_TOOLS,
  _TOOL_ACTIONS, _INTENT_TRIGGER_WORDS, _should_skip_intent(), and the entire
  round-0 intent-first + write-tool confirmation block (~315 lines removed)
- research_topic tool calls are now handled inline in the streaming loop:
  runs run_research_pipeline, streams synthesis to buf, then breaks the round
  loop (research is still the full response, no model follow-up)
- config.py: raise OLLAMA_NUM_CTX default from 8192 to 16384

The quick-capture dedicated classifier (classify_capture_intent) is unchanged.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 18:30:21 -05:00
bvandeusenandClaude Sonnet 4.6 1106399883 Quick-capture notes: enrich content via main model
Add _process_note() — a second LLM pass using the main model that
transforms raw capture text into a well-formed note with a genuine
summary title and formatted body. Replaces the previous behaviour of
using the captured text verbatim as both title and body.

The processing prompt instructs the model to:
- Generate a 3-8 word summary title (never a verbatim copy)
- Format the body appropriately: bullet lists for items, clean prose
  for stream-of-thought, organised paragraphs for raw notes/fragments
- Preserve all original information without inventing new facts

The enrichment pass runs for both the intent-classified create_note
path and the fallback path. On LLM/parse failure it degrades safely
to the old verbatim behaviour.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 17:57:29 -05:00
bvandeusenandClaude Sonnet 4.6 2c2874a1cc Fix streaming timeout and false-offline status indicator
- stream_chat and stream_chat_with_tools: remove read=300s per-chunk
  timeout, replace with read=None. In httpx streaming mode, the read
  timeout applies per-chunk — if Ollama pauses >300s while processing
  a large input context before the first token, it raises ReadTimeout,
  killing generation and leaving the assistant message as an empty stub.
  With read=None the stream is unbounded; connect=30s still guards the
  initial connection.

- chat_status_route: increase Ollama status check timeout 5s → 10s.
  When Ollama is busy processing a large prompt it can be slow to
  respond to /api/tags, causing the status indicator to briefly flip to
  "offline" even though generation is running normally.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-02 17:50:42 -05:00
bvandeusenandClaude Sonnet 4.6 6f5170854b Remove CalDAV todo tools; overhaul quick-capture
- Remove all 6 CalDAV todo tools (create/list/update/complete/delete/search_todos)
  from tools.py definitions, imports, execute_tool branches, intent routing rules,
  generation_task labels/actions, and llm.py system prompt hints. CalDAV event
  tools remain. Todo functions still exist in caldav.py but are no longer exposed.

- Quick-capture now uses a dedicated classify_capture_intent() with a focused
  _CAPTURE_SYSTEM_PROMPT that always routes to a tool (never null). Tool set
  expanded: create_note/task/event + update_note + research_topic.

- research_topic in quick-capture calls run_research_pipeline() directly (no SSE
  buffer). run_research_pipeline() now accepts buf=None; all buf.append_event
  calls are guarded so status events are skipped when no buffer is provided.

- Fallback note now always sets body=text (was empty for texts ≤80 chars).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 19:13:07 -05:00
bvandeusenandClaude Sonnet 4.6 4c77eab84b Fix image serving: use cache_timeout not max_age (Quart API)
Quart's send_file uses cache_timeout= not max_age=. The TypeError on
every /api/images/<id> request caused a 500, which the browser rendered
as alt text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 14:38:54 -05:00
bvandeusenandClaude Sonnet 4.6 1890f0c7f1 Fix image search: embed markdown instead of describing URLs
- tools.py: search_images result now includes 'embed' (ready-to-use
  markdown image syntax) and 'citation' fields instead of raw 'local_url';
  adds 'instructions' field so the model knows to render them verbatim
- llm.py: system prompt now explicitly tells the model to embed images
  using the 'embed' field rather than describing or listing URLs
- markdown.ts: explicitly allow src/alt in PURIFY_OPTS_FULL so img tags
  are never stripped by DOMPurify

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 13:19:59 -05:00
bvandeusenandClaude Sonnet 4.6 de1831f689 Update summary.md for session: image cache, suggested notes improvements
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 13:09:01 -05:00
bvandeusenandClaude Sonnet 4.6 c962e86039 Broaden app volume mount from /data/images to /data
Single named volume app_data covering the entire /data directory so
future persistent storage (uploads, exports, etc.) doesn't need
additional volume entries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 12:58:33 -05:00
bvandeusenandClaude Sonnet 4.6 efe0a15b8c Add image search with local cache (Phase 23)
Images found via SearXNG are fetched server-side, stored on disk, and
served from /api/images/<id> — the user's browser never contacts the
original image host.  Original URLs are preserved for citation.

New files:
- alembic/versions/0016_add_image_cache.py  — image_cache table
- src/fabledassistant/models/image_cache.py — SQLAlchemy model
- src/fabledassistant/services/images.py    — fetch/store/serve logic
- src/fabledassistant/routes/images.py      — GET /api/images/<id>

Modified:
- config.py: IMAGE_CACHE_DIR (/data/images), IMAGE_MAX_BYTES (5 MB)
- research.py: _search_searxng_images() — SearXNG categories=images
- tools.py: _IMAGE_TOOLS def + search_images branch in execute_tool
- intent.py: search_images routing rule (explicit visual language only)
- app.py: register images_bp
- docker-compose.yml: image_cache named volume mounted at /data/images
- ToolCallCard.vue: "image_search" label

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 12:55:10 -05:00