387 Commits

Author SHA1 Message Date
bvandeusen f953a36b86 feat(header): pill nav bar, brand shortening, status pulse, header gradient 2026-04-08 11:11:35 -04:00
bvandeusen 4bc7f9eaac feat(theme): shift palette from indigo to deep violet + muted gold
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-08 11:09:23 -04:00
bvandeusen a684d84edf feat: deprecate /notes and /tasks routes; redirect to Knowledge view 2026-04-08 10:35:21 -04:00
bvandeusen 34729cf1cf feat(knowledge): add task cards with status/priority/due-date display 2026-04-08 10:32:51 -04:00
bvandeusen 6e0c528126 fix(voice): resume AudioContext to prevent suspended state blocking silence detection 2026-04-07 22:54:41 -04:00
bvandeusen 56b687c9f4 fix(voice): show mic button on HTTP; graceful error for non-secure contexts 2026-04-07 20:06:57 -04:00
bvandeusen 6d57840dc7 fix(voice): re-check voice status on tab visibility change and after saving voice settings 2026-04-07 20:01:47 -04:00
bvandeusen 7f37cee49f refactor(voice): integrate click-to-toggle mic into ChatInputBar; remove VoiceOverlay 2026-04-07 13:17:25 -04:00
bvandeusen d290bebad2 feat(stt): pass conversation context as Whisper initial_prompt to reduce mishearings 2026-04-07 08:48:27 -04:00
bvandeusen 814f44c3fb fix(push): fix VAPID key format and add regenerate endpoint + UI button 2026-04-07 06:59:06 -04:00
bvandeusen a5e35f7c72 feat: mount VoiceOverlay and wire Space bar shortcut in App.vue 2026-04-06 20:20:19 -04:00
bvandeusen 853dc810ff feat: click-to-toggle silence detection and amplitude bars in VoiceOverlay 2026-04-06 20:01:44 -04:00
bvandeusen ac7dde472f feat: expose live stream ref from useVoiceRecorder 2026-04-06 20:00:22 -04:00
bvandeusen 84926d4ba2 feat: add useSilenceDetector composable with Web Audio API amplitude monitoring 2026-04-06 19:48:32 -04:00
bvandeusen b9d0716b01 feat: add News link to main navigation header
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 17:49:22 -04:00
bvandeusen 9af8ab8f70 fix(briefing): use briefing context for follow-ups; add slot separator
- build_context: when conversation_type is 'briefing', inject a system
  prompt instruction telling the model to answer from conversation history
  and article context instead of searching the web
- Consolidate briefing conversation type detection to one DB query (was
  being checked twice — once for the system prompt addition, once for
  article context injection)
- ChatPanel: render a visual 'New Briefing Update' separator line before
  2nd+ briefing slot messages (identified by metadata.rss_item_ids)
- types/chat.ts: add metadata field to Message interface

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-06 06:15:18 -04:00
bvandeusen e4c812a603 feat(voice): improve TTS logging for root-cause diagnosis
- Route now logs every synthesis request (char count, voice, speed)
- Route logs char count + text preview when the 8000-char limit is hit
- Route logs empty audio with preview (helps spot no-chunk-produced edge case)
- Route logs success with byte count and duration
- Kokoro synthesise() logs per-call: samples produced, elapsed, chars/s
- Kokoro synthesise() logs warning when zero audio chunks returned with preview
- Kokoro synthesise() catches and logs pipeline-internal errors with preview
- Frontend: console.warn now includes char count + 80-char preview on failure and retry

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 22:36:43 -04:00
bvandeusen c8c0de3b04 feat(settings): SSO account guard + remove redundant Office Days
- Account tab: SSO users see an info banner instead of email/password forms
- Briefing tab: remove Office Days section (work days now come from Profile)
- Remove unused toggleWorkDay function

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:41:26 -04:00
bvandeusen 1357046160 feat(settings): add timezone field to General tab
- New Timezone section with text input + Detect button
- Detect auto-fills from browser Intl API
- Save calls PUT /api/settings (which now propagates to scheduler)
- Briefing tab firing timezone hint reads stored value instead of live browser API

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 21:39:08 -04:00
bvandeusen 284dcd1c63 feat(briefing): add article Discuss endpoint with synthetic tool exchange
POST /api/briefing/articles/<id>/discuss injects stored article content
as a persisted read_article tool exchange before triggering generation.
The LLM sees the article as already read; follow-ups retain context via
the fixed history builder. Frontend Discuss button now calls the new
endpoint instead of inlining article text in the user message.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 19:36:45 -04:00
bvandeusen 94d21c4512 fix(settings): audit pass — model auto-pull on startup, background_model empty-string bug, base URL validation
- Startup now pulls Config.OLLAMA_MODEL (system default chat model) — previously only
  embedding and background models were pulled; the primary chat model was skipped
- _warm_user_models expanded to also pull user-configured default_model and
  background_model overrides that are missing from Ollama, rather than logging and
  skipping them; pulls run before warm/KV-cache priming
- Add background_model to _MODEL_KEYS in settings route so clearing the dropdown
  deletes the DB row instead of saving "", which caused Ollama failures in tag
  suggestions, title generation, project summaries, and RSS classification
- Add http/https scheme validation to PUT /api/admin/base-url matching the CalDAV
  route pattern; a bad value no longer silently breaks invite/password-reset links
- Update admin voice config description: "Reload models" button exists to avoid
  a server restart, so the old "restart required" text was misleading

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:08:14 -04:00
bvandeusen d9bd16633f fix(tasks): audit pass — permission checks, tool gaps, hard reload
- PUT/PATCH/DELETE /api/tasks/:id now use get_note_for_user + can_write_note
  so shared project editors can mutate tasks; owners unaffected
- PATCH /api/tasks/:id/status gets same treatment
- All write routes call update_note/delete_note with note.user_id (owner)
  not the accessing user's uid, matching the milestone fix pattern
- create_task tool gains tags (array) and status (enum) parameters;
  handler now passes tags to create_note and respects initial status
- create_task tool response now includes milestone_id and parent_id
- update_note tool gains milestone parameter; handler resolves the
  milestone by title within the note's current (or newly set) project,
  clears milestone_id when project is cleared
- list_tasks tool gains q keyword search parameter; passed through
  to list_notes
- TaskEditorView: replace window.location.reload() with
  router.push('/tasks/:id') after save

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 13:48:07 -04:00
bvandeusen c5191837fb fix(projects): audit pass — 8 correctness and consistency fixes
- Project.to_dict() now includes user_id and auto_summary
- Status validation unified to (active/completed/archived) on both
  create and update project routes; update route previously had none
- Milestone routes: replace get_project (ownership-only) with
  get_project_for_user so shared viewers/editors can access milestones
- Add get_milestone_in_project() to milestones service for project-
  scoped lookup without user_id filter; all milestone routes use it
- Milestone PATCH now validates status as 'active'|'done'; fix tool
  enum which was wrongly ['active','completed','cancelled']
- Write mutation routes (POST/PATCH/DELETE milestones) now check
  can_write_project() and return 403 for read-only shared users
- update_project tool now exposes title and color fields so projects
  can be renamed or recolored via chat
- create_project tool now exposes color field
- GET /api/projects?include_summary=true embeds summaries in one
  backend pass; ProjectListView switches to this, eliminating N+1
  per-project fetches

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 13:14:42 -04:00
bvandeusen ed715dcc23 feat(knowledge): note types, counts, new-note button, audit fixes
- Add note_type (note/person/place/list) selector + entity metadata fields
  (relationship, email, phone / address, hours) to NoteEditorView
- Pre-select type via ?type= query param from KnowledgeView new-note dropdown
- KnowledgeView: add split "New note / ▾" button with type dropdown
- KnowledgeView: show per-type counts on sidebar filter buttons (when > 1)
- Fix: filter-btn now flex layout so count badge aligns to right edge
- Fix: list_notes count_query was missing parent_id filter (inflated totals)
- Fix: PATCH /api/notes/:id now fires upsert_note_embedding (workspace autosave)
- Fix: get_knowledge_counts endpoint for per-type counts
- Fix: get_knowledge_tags was silently discarding note_type filter (double-stmt bug)
- Fix: NoteEditorView onMounted stray brace from edit session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:53:09 -04:00
bvandeusen 358534efbf fix(events): audit pass — 7 correctness fixes across the events system
Backend:
- tools.py: apply UTC normalization to update_event datetime fields
  (matched create_event which already did this)
- events.py service: allow end_dt/recurrence/project_id to be cleared
  via update_event by permitting None for nullable fields
- events.py service: find_events_by_query now returns upcoming events
  first, falling back to past — prevents AI tools from mutating stale
  past events when a future match exists
- events.py service: list_events now uses overlap logic (start <= to
  AND end >= from) so multi-day events spanning the query boundary
  are included; previously only start_dt was checked

Frontend:
- ToolCallCard: fire fable:calendar-changed on created/updated/deleted
  so CalendarView refetches without requiring a manual page refresh
- KnowledgeView: replace raw apiGet('/api/events') with listEvents()
  client function; also fix today bar which was reading .events off a
  flat array (always empty) — now correctly receives EventEntry[]
- HomeView: use full ISO strings for event date range instead of naive
  UTC-midnight strings; deduplicate inline date math via _dateRange()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:02:10 -04:00
bvandeusen 7677ab4028 fix(calendar): use date-only strings for all-day events to prevent timezone shift
UTC midnight passed to FullCalendar's timeZone:'local' was being
converted to local time, shifting all-day events back by 1+ days for
users in UTC-X zones. The edit form had the same bug via new Date().

Fix: pass YYYY-MM-DD slices (UTC date) for all-day events in both
toFcEvent and EventSlideOver resetForm, bypassing timezone conversion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 11:01:51 -04:00
bvandeusen 90afbec4c2 feat(knowledge): two-tier pagination — ID pre-fetch + content batch loading
Backend:
- GET /api/knowledge/ids: returns up to 100 note IDs cheaply (no body
  parsing), supports same filters as /api/knowledge, includes has_more
- GET /api/knowledge/batch?ids=...: fetches full items for given IDs in
  order; used by frontend to load content in controlled batches

Frontend (KnowledgeView):
- Fetch 100 IDs upfront, load first 50 as content on mount
- IntersectionObserver sentinel (root: null) triggers 24-item content
  batches as user scrolls
- Proactive ID refill when queue drops below 48 unloaded IDs
- fetchGen counter invalidates stale in-flight responses on filter reset
- IDs claimed before async fetch to prevent double-loading
- sentinelVisible ref drives post-load re-check when content doesn't
  push sentinel off screen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 09:44:50 -04:00
bvandeusen 5495fd1500 fix(knowledge): debounce scroll handler with rAF to prevent duplicate page loads
Coalesces rapid scroll events into one check per animation frame so
that page++ can only fire once per frame, eliminating the window where
multiple events slip through before loading=true is observed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 09:25:52 -04:00
bvandeusen 1fc0004e93 fix(knowledge): replace IntersectionObserver with scroll event; increase card height
- Swap IntersectionObserver (race-prone, fired immediately on creation)
  for a passive scroll listener on the grid container — eliminates
  duplicate page loads caused by observer re-creation after DOM updates
- Increase card min-height 100px → 160px so tags + snippet are visible
- Increase snippet line-clamp 3 → 4 for more content preview

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 00:24:19 -04:00
bvandeusen 3c38c04ad4 fix(knowledge): sentinel DOM ordering + restore tag visibility
Sentinel was first in DOM with order:9999, causing layout recalculation to
trigger IntersectionObserver multiple times (intersecting→not→intersecting)
as items were appended, producing duplicate pages. Move sentinel to AFTER
the v-for items so it's naturally last in both DOM and visual order.

Remove overflow:hidden from .k-card-tags — .k-card overflow:hidden already
clips escaping content; the extra overflow on the tags container was
collapsing its height to zero and hiding all tag pills.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 22:54:35 -04:00
bvandeusen 4ac26d9326 fix(knowledge): clip card overflow so tags don't escape card boundary
Added overflow:hidden to .k-card so wrapped tags are clipped within the
card border-radius. Added min-width:0 + overflow:hidden on .k-card-tags
so the flex item can shrink properly and doesn't push past the card width.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:57:28 -04:00
bvandeusen 00abfcf4db fix(knowledge): remove backup load check causing duplicate items on scroll
The IntersectionObserver fires as soon as it's created (sentinel immediately
intersecting after page 1 renders), while the removed backup check also fires
in the same tick — two concurrent fetchItems(page 2) calls produced duplicate
cards. With sentinel now properly inside the scrolling root, the observer
alone handles progressive loading.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:55:11 -04:00
bvandeusen 3887cab66e fix: knowledge infinite scroll + list_events timezone handling
KnowledgeView: sentinel was OUTSIDE the card-grid div, making
IntersectionObserver (root: cardGridEl) never fire since the target must
be a descendant of the root. Moved sentinel inside card-grid with
grid-column:1/-1 + order:9999 so it spans all columns and sits at the
bottom. Fixed backup check to compare against container bounds not viewport.

tools.py list_events: apply same UTC normalization as create_event (treat
naive datetimes as UTC, handle Z suffix). Update tool description to
explicitly request full-day UTC ranges so the LLM doesn't send local time
without offsets, which caused the recall query to miss UTC-stored events.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 21:10:43 -04:00
bvandeusen aeb778f35a feat(calendar): month/year picker popover on title click
Click the month name in the FullCalendar toolbar to open a popover with
prev/next year arrows and a 4×3 month grid. Clicking a month jumps the
calendar to that month via gotoDate(). Current month highlighted. Picker
closes on outside click. Title gains hover highlight + pointer cursor.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 20:27:01 -04:00
bvandeusen eda9c5ce43 feat: migrate KnowledgeView mini-chat to ChatPanel + enable note picker everywhere
- KnowledgeView: replace custom mini-chat (voice, PTT, manual scroll, message
  rendering) with ChatPanel variant="full"; gains RAG scope chip, TTS listen
  mode, volume control, and note picker automatically (-218 lines)
- ChatInputBar: remove briefingMode guard on note picker so attach/search works
  in briefing, workspace, widget, and knowledge chat surfaces

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 20:16:36 -04:00
bvandeusen d624d38412 fix: KnowledgeView infinite scroll root + calendar event refresh
- KnowledgeView: IntersectionObserver was watching the viewport instead
  of the card-grid scroll container, causing infinite scroll to stop
  loading after only ~29 items. Pass card-grid element as `root`.
- CalendarView: listen for 'fable:calendar-changed' custom event and
  call refetchEvents() so tool-created events appear without navigation.
- ChatPanel: dispatch 'fable:calendar-changed' when create_event,
  update_event, or delete_event tool calls succeed.
- tools.py: normalize naive datetimes to UTC before storing events so
  timezone comparisons in list_events queries are always consistent.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 19:28:33 -04:00
bvandeusen 86e718dda1 refactor: migrate HomeView to ChatPanel widget, delete DashboardChatInput
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 18:36:13 -04:00
bvandeusen 0b1ed2afe5 refactor: migrate WorkspaceView chat to use ChatPanel component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 17:47:17 -04:00
bvandeusen 2c446be83a refactor: migrate BriefingView chat to use ChatPanel component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 16:50:18 -04:00
bvandeusen f36398f892 refactor: migrate ChatView to use ChatPanel component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 16:41:22 -04:00
bvandeusen 927f137aaf feat: rewrite ChatPanel with full and widget variants
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 16:37:20 -04:00
bvandeusen 7eaf4d9dca feat: add ChatStreamingBubble extracted streaming state component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 16:30:05 -04:00
bvandeusen 89a9088b94 feat: add ChatInputBar unified input bar component
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 15:57:51 -04:00
bvandeusen 0a913045a8 fix(tts): play() must return a promise that resolves when audio finishes
Without this, await audio.play() resolves immediately after source.start(),
so the playQueue chains the next sentence before the current one finishes,
causing overlapping / interrupted playback.
2026-04-03 14:21:44 -04:00
bvandeusen c81a499e6e fix(tts): add speak() for complete responses; fix briefing message commit race 2026-04-03 14:18:34 -04:00
bvandeusen 8024706870 fix(knowledge): fill viewport on load when sentinel stays visible 2026-04-03 14:07:52 -04:00
bvandeusen 9d519054ee feat(tts): add streaming TTS listen mode to WorkspaceView 2026-04-03 13:15:28 -04:00
bvandeusen b4f5a935b2 feat(tts): wire useStreamingTts into BriefingView 2026-04-03 13:11:12 -04:00
bvandeusen fda6a7acc1 feat(tts): wire useStreamingTts into ChatView 2026-04-03 13:09:52 -04:00
bvandeusen 7ffd412603 feat(tts): add useStreamingTts composable for sentence-level streaming 2026-04-03 12:54:05 -04:00