387 Commits

Author SHA1 Message Date
bvandeusen 3bd0dc6879 feat(settings): add background model picker with KV cache performance warning
Exposes OLLAMA_BACKGROUND_MODEL as a per-user setting in General settings,
alongside the Chat Model selector. Includes an inline warning when the same
model is selected for both, explaining the KV cache performance impact.

All background task callers (title generation, tag suggestions, project
summaries, RSS classification) now read background_model from user settings,
falling back to OLLAMA_BACKGROUND_MODEL env var.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 10:44:08 -04:00
bvandeusen 750a91898a perf(llm): route background tasks to dedicated model to preserve KV cache
Background tasks (title generation, tag suggestions, project summaries,
RSS classification) were using qwen3:8b and wiping its KV cache after
every response, preventing prefix cache hits on subsequent user messages.

Adds OLLAMA_BACKGROUND_MODEL (default: qwen2.5:0.5b) config var and
routes all background LLM calls to it, keeping qwen3:8b's KV cache
warm between user messages for consistent sub-second TTFT.

Also adds infinite scroll to KnowledgeView (replaces load-more button)
and bakes spaCy en_core_web_sm into the Docker image to eliminate the
pip install on every startup.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 01:33:54 -04:00
bvandeusen a473f6e039 fix: minichat markdown rendering and weather temperature unit preference
- KnowledgeView minichat: render assistant messages through renderMarkdown
  so headers, bold, lists etc. display correctly instead of raw markdown
- get_weather tool: read user's temp_unit from briefing_config and convert
  temperatures to °F when preferred; also include temp_unit in the
  returned payload so the model can label values correctly

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 00:52:54 -04:00
bvandeusen 07f4956550 fix: center minichat widget and fix weather tool success status
- KnowledgeView minichat: add margin-inline: auto so the widget centers
  within the content area when max-width is reached on wide screens
- weather get_weather tool: return success: true on both the arbitrary
  location path and the cached locations path so ToolCallCard shows
  the correct success state instead of always flagging as error

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-03 00:50:42 -04:00
bvandeusen 36634919cc fix(home): center quick chat widget and constrain width
Chat section and inline response now have max-width 720px centered
within the page, and quick action chips are centered. Prevents the
widget from stretching the full 1200px content width on wide screens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 23:26:53 -04:00
bvandeusen 749a60b9fd feat: interactive checkboxes in list note viewer
- renderMarkdown() accepts interactiveCheckboxes option: removes disabled=""
  and stamps data-task-index on each checkbox in the marked HTML output
- NoteViewerView detects list notes by body content (- [ ] / - [x] pattern)
  and passes interactiveCheckboxes: true when rendering
- onBodyChange() handles checkbox change events: toggles the matching line
  in the body, optimistically updates the store, then PATCHes the note
- prose.css adds .prose--checklist rules for marked output: no bullet,
  flex row, accent-color, line-through on checked items via :has()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 09:31:37 -04:00
bvandeusen 9549eb85bc feat: live card refresh, list checkboxes, minichat width cap
KnowledgeView:
- Watch streamingToolCalls; call fetchItems+fetchTags on create/update
  note or task so the card grid reflects changes made via the minichat
- Cap minichat to max-width: var(--page-max-width) so it matches chat column width

WorkspaceView + WorkspaceNoteEditor:
- Expose reload() from WorkspaceNoteEditor via defineExpose
- Call noteEditorRef.reload() alongside taskPanelRef.reload() when
  create_note/update_note tools succeed in the SSE watcher

KnowledgeView list cards:
- Backend: parse markdown task list into list_items [{text, checked}] + body
- Card renders up to 6 items with real checkboxes; toggleListItem()
  does an optimistic update then PATCHes /api/notes/:id
- Progress bar kept below items; "+N more" shown when list is long

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 21:30:28 -04:00
bvandeusen 23a7ed7822 refactor: DRY layout bounds via --page-max-width and --sidebar-width CSS vars
Add --page-max-width (1200px), --page-padding-x (1rem), and --sidebar-width (260px)
to theme.css so all views share a single source of truth.

- HomeView: 1100px → var(--page-max-width) (aligns with all other views)
- NotesListView, TasksListView, ProjectListView, ProjectView, CalendarView: var(--page-max-width)
- ChatView: sidebar + context sidebar → var(--sidebar-width); inner message/input
  column max-widths → var(--page-max-width)
- KnowledgeView: filter panel 180px → var(--sidebar-width); minichat left offset updated

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 20:56:33 -04:00
bvandeusen d5771a3d5c fix: remove view max-width constraints; widen graph panel
- Drop max-width from .knowledge-root so the graph panel can use the full
  viewport width without hitting a cap
- Drop max-width from .chat-page (message bubbles are already self-constraining)
- Increase normal graph panel width 420px → 500px
- Increase expanded width to min(960px, 60vw) so it scales with the viewport
  and updates minichat right-offset to match

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 20:39:21 -04:00
bvandeusen 0d12d01115 feat: constrain Chat/Knowledge width + persistent expandable graph panel
Layout:
- Chat and Knowledge views now max out at 1600px and center on wide
  screens, consistent with the rest of the app; app-content overflow
  is set to hidden for both so they manage their own scroll

Graph panel (Knowledge):
- Open/closed state persisted to localStorage (fa_knowledge_graph_open)
  — stays open across navigation and page refreshes
- Expanded state persisted (fa_knowledge_graph_expanded): chevron button
  in the panel header toggles between 420px (normal) and 700px (expanded)
- Minichat right offset follows the panel width with a matching transition

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 16:01:18 -04:00
bvandeusen d96895c276 fix: briefing refresh — weather not populated + workspace conv bleed
Two bugs:

1. Manual 'Refresh' button didn't refresh weather/RSS before compiling.
   The daily scheduler calls refresh_all_feeds + refresh_location_cache
   before run_compilation; the manual trigger route called run_compilation
   directly, reading a stale cache. Manual trigger now mirrors the
   scheduler: refresh feeds and all configured weather locations first.

2. Navigating to WorkspaceView during a long briefing compilation caused
   the workspace chat to show the briefing content. triggerNow awaits
   ~30-60s; on completion it called loadAll() → chatStore.fetchConversation
   which overwrote the workspace's currentConversation in the shared store.
   Fixed with a _mounted flag — all post-async state writes in BriefingView
   are now guarded so they no-op if the component has been unmounted.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 12:46:53 -04:00
bvandeusen 145c18d8a3 refactor: DRY calendar additions + define --color-surface in theme
- Define --color-surface in theme.css (light: #f0f0f8, dark: #1a1b22)
  — was used across 10+ components with no-op fallbacks; now properly
  defined alongside --color-bg and --color-bg-card
- Extract shared date formatters into utils/dateFormat.ts:
  fmtTime, fmtDateTime, fmtRelativeDateTime, fmtDayLabel, fmtCompact
- Replace duplicate inline formatters in CalendarView, HomeView
  (formatUpcomingTime), and KnowledgeView (formatEventDate)
- CalendarView: replace hardcoded rgba(99,102,241,0.4) hover colour
  with color-mix(in srgb, var(--color-primary) 40%, transparent);
  fix --color-input-bg fallback to use var(--color-bg); remove
  hardcoded hex fallbacks now that --color-surface is defined

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 12:27:23 -04:00
bvandeusen f96013a4bc feat: calendar event popover + upcoming events strip
B — Event popover: clicking a calendar event shows a compact overlay with
    full details (title, time range, location, description, color accent)
    and Edit/Close actions; positioned relative to the click, closes on
    outside click; Edit opens the existing EventSlideOver

C — Upcoming strip: scrollable section below the calendar showing the
    next 4 weeks of events grouped by day (Today/Tomorrow/date label),
    each card with color accent bar, title, time, location, description
    snippet; clicking a card opens EventSlideOver for edit

Both features stay in sync with create/update/delete operations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 12:10:16 -04:00
bvandeusen 30981a3121 feat: persist listen mode across views via localStorage
Extract listen mode into a shared useListenMode() composable backed by
localStorage ('fa_listen_mode'). ChatView and BriefingView both use it,
so toggling auto-read on in one view keeps it on after navigation or
page refresh — no need to re-enable it each visit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 11:53:03 -04:00
bvandeusen 71b8c5965c fix: briefing discuss button — full article content + scroll + optimistic UI
Three bugs in discussArticle():
- Scroll selector was '.briefing-chat' (doesn't exist) → '.briefing-center';
  the panel never scrolled into view so the response was invisible until refresh
- Only 300-char snippet was sent to the LLM; now passes the full stored
  content (up to 2000 chars) from the backend
- User message wasn't shown until streaming ended; now added optimistically
  to messages[] immediately on click so it appears straight away

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 11:46:23 -04:00
bvandeusen 5924e565b1 fix: floating mini-chat overlay style + weather precip fallback
- KnowledgeView mini-chat: replace harsh border-top with upward box-shadow
  and rounded top corners (16px); remove padding-bottom from content area
  so widget truly overlays cards without pushing layout; add collapse
  toggle (chevron) that hides messages without closing the conversation
- WeatherCard: show precip_mm as fallback when precipitation_probability_max
  is null but actual rainfall is expected (Open-Meteo omits probability for
  some forecast days even when rain is shown)
- Pass precip_mm through weather service → frontend type definitions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 09:04:47 -04:00
bvandeusen a0620c4949 fix: knowledge view — chat input styling, autofocus, graph component (no iframe)
- Mini-chat input bar now uses shared --color-input-bar-* CSS variables and
  the same chat-input-bar pill pattern as all other chat interfaces
- chatInputEl focused on mount (autofocus on page load)
- Graph panel: replaced iframe (blocked by X-Frame-Options: DENY) with
  inline GraphView component; CSS :deep override constrains its height
  to fill the panel instead of 100vh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 18:49:02 -04:00
bvandeusen 80f30b705d feat: Knowledge view + entity types (People, Places, Lists)
Data model:
- Migration 0036: adds note_type TEXT (default 'note') and metadata JSONB
  to the notes table; index on note_type
- Note model: entity_type property, note_type/metadata in to_dict()
- create_note() accepts note_type and metadata params

Backend:
- /api/knowledge — unified paginated endpoint: type/tag/sort/q filters,
  semantic search via embeddings, excludes tasks
- /api/knowledge/tags — distinct tags across knowledge objects
- New LLM tools: create_person, create_place, create_list, add_to_list,
  clear_checked_items — all wired into execute_tool()
- People and places auto-injected as compact summary into LLM system prompt

Frontend:
- KnowledgeView replaces HomeView at /; left filter panel (type+tag),
  toolbar (search, sort, graph toggle), card grid with type-aware cards
  (indigo=note, emerald=person, amber=place, sky=list), load-more pagination
- Today bar: upcoming events, overdue task count, Briefing/Chat links
- Floating mini-chat sticky to bottom: creates/continues a conversation
  inline, message history expands upward, close button ends session
- Graph panel: toggles as a 420px right panel at full viewport width
- AppHeader: Knowledge, Chat, Briefing, Calendar, Tasks, Projects
- Router: / → KnowledgeView; /knowledge redirect; HomeView import removed

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 18:01:03 -04:00
bvandeusen 882ea176b2 fix: briefing discuss prompt — suppress research tool to prevent full research note
Explicitly instruct the LLM to respond conversationally and not use any
research/search tools when summarizing a shared article excerpt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 16:54:18 -04:00
bvandeusen baeb0b14e5 feat: listen mode + volume knob in chat; briefing discuss auto-send; fix LLM proactive note search
- ChatView: listen mode toggle (auto-reads new responses via TTS), volume popup
  with range slider persisted per-device in localStorage via GainNode
- useVoiceAudio: shared module-level _volume ref with localStorage persistence,
  GainNode for volume control, exported setVoiceVolume()
- tts.py: pre-warm all Kokoro voices at pipeline load to eliminate HuggingFace
  HEAD requests at synthesis time (reduces TTS latency)
- BriefingView: discuss article button now auto-sends instead of just filling input;
  prompt capped to 15 sentences; send() accepts optional overrideText
- llm.py: instruct LLM not to proactively search notes or comment on note absence

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 16:52:11 -04:00
bvandeusen ab397e78f3 fix: briefing TTS now uses saved voice/speed/blend settings
synthesiseSpeech() called without explicit params now omits voice/speed/
blend from the request body. The backend detects this and auto-loads all
three from the user's saved settings (voice_tts_voice, voice_tts_speed,
voice_tts_blend), so briefing listen mode respects the voice the user
configured in Settings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 13:46:52 -04:00
bvandeusen ea23f16bd7 feat: weather card — precip probability %, condition text, unit-aware wind
- Fetch precipitation_probability_max from Open-Meteo (replaces precip_sum
  in the card display — probability is more useful at a glance than mm)
- Show WMO condition description text on each forecast day
- Convert wind speed to mph when temp unit is F; pass wind_unit in response
- Display 💧 X% chance of rain; 💨 X mph/km/h wind per day

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 13:02:36 -04:00
bvandeusen c1fcb1e287 feat: discuss article in briefing chat via 💬 button on news cards
Clicking 💬 on a news card in the briefing panel pre-fills the briefing
chat input with the article title, snippet, and source so the user can
ask the briefing LLM to summarize or discuss it directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 12:57:25 -04:00
bvandeusen c31cf11767 fix: use AudioContext for voice previews to bypass browser autoplay policy
new Audio().play() after an async await loses the user gesture context and
is silently blocked. Creating AudioContext synchronously before the fetch
preserves the permission, then decode/play through it after the await.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 12:55:07 -04:00
bvandeusen 71ca0ecb5c fix: voice status global store, per-view mic reactivity, single-voice preview
- Move voice status into settings store (voiceSttReady, voiceTtsReady),
  checked once at login and refreshed after admin model reload
- ChatView, BriefingView, DashboardChatInput now use computed refs from
  the store — mic buttons appear reactively without needing a page reload
- BriefingView: separate STT-only guard for mic PTT vs TTS-only guard for
  listen mode / speak buttons
- Add ▶ Preview button to Voice & Speed section in Settings for single-
  voice testing without enabling blend mode

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:53:06 -04:00
bvandeusen b4b4b0d9d6 feat: weather precip/wind, dashboard mic, remove global voice overlay
- WeatherCard: show precipitation (mm) and max wind speed per forecast day
- DashboardChatInput: add PTT mic button (transcribe-to-input, voice-gated)
- Remove global VoiceOverlay floating button and Space PTT shortcut from
  App.vue — inline mic buttons in chat/briefing/dashboard are the right UX;
  global overlay had focus/latency/context issues

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 20:00:06 -04:00
bvandeusen 76c3dbc4b7 fix: stop TTS playback when PTT is activated
Pressing push-to-talk now immediately stops any ongoing TTS audio before
opening the microphone, preventing the assistant from hearing itself.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:43:15 -04:00
bvandeusen 1460863e82 feat: Kokoro voice blending — blend builder UI + weighted tensor synthesis
Add voice blend support to TTS pipeline and settings UI. Users can mix
2–5 Kokoro voices with per-voice weight sliders; the blended style tensor
replaces the single voice when enabled. Settings persist as JSON and auto-
load on synthesis when no explicit voice is supplied in the request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 19:10:22 -04:00
bvandeusen 98b3cdb593 feat: add weather condition icons to WeatherCard
Maps WMO condition strings to emoji icons for current conditions and
forecast days. No external dependencies — pure emoji lookup by condition text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 18:08:18 -04:00
bvandeusen a773c11aa0 feat: RSS embeddings, semantic news in chat, article-to-chat, richer briefings
- Embed RSS items at fetch time (nomic-embed-text); backfill at startup
- Semantic news search injected into chat system prompt ("Recent News You've Seen")
  when items match query above 0.55 cosine threshold (independent of note RAG)
- "Discuss in chat" button on news cards — creates a seeded conversation with
  the article title + full content, navigates directly to the new chat
- Briefing compilation now passes 500-char article excerpts (not just headlines)
  to the LLM and uses 8192 num_ctx to accommodate the larger prompt

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 15:12:38 -04:00
bvandeusen dba41879ed feat: structured user profile with LLM-learned preferences
Replaces the freeform briefing-profile note with a DB-backed user_profiles
table. Users can edit job/industry/expertise/response preferences/interests/
work schedule via a new Settings → Profile tab. The LLM appends nightly
observations; at 14+ entries they are auto-consolidated into a learned_summary.
Profile context is injected into both briefing and chat system prompts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-30 14:17:30 -04:00
bvandeusen dd304bb556 feat: unify voice PTT across briefing and chat views
- ChatView: add PTT mic button in input bar (hold to speak → transcribe → send)
- BriefingView: restyle input bar to match ChatView (floating pill, circle send)
- BriefingView: move listen/mic controls into input bar, remove from header
- BriefingView: consistent icon-button style for speaker/mic matching ChatView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 21:58:20 -04:00
bvandeusen f146485df3 feat: hot-reload voice models without server restart
Voice enabled/STT model are now DB-backed (admin settings), not env
vars. Added reload_stt_model()/reload_tts_model() that clear singletons
under lock and re-trigger loading. POST /api/admin/voice/reload triggers
both in background tasks. Settings UI polls /api/voice/status every 2.5s
until models are ready, with spinner feedback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 20:52:07 -04:00
bvandeusen eaf70500b8 feat: move voice enable/model config to admin UI
Replace VOICE_ENABLED env var gate with DB-backed admin setting.

- services/voice_config.py: reads voice_enabled + voice_stt_model from
  admin user's settings row (falls back to env var defaults)
- routes/admin.py: GET/PUT /api/admin/voice for admin configuration
- routes/voice.py, services/stt.py, services/tts.py: read enabled/model
  from DB via voice_config instead of Config directly
- app.py: always schedule model loaders at startup; they self-gate on
  the DB setting so no conditional needed at the call site
- SettingsView.vue: Voice section in Admin → Config tab (enable toggle +
  STT model dropdown); user Voice tab now points to admin panel when disabled

No env var required to test — enable via Settings → Admin → Config → Voice.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 20:22:18 -04:00
bvandeusen 6f84d90dff feat: voice S2S — faster-whisper STT, Kokoro TTS, PTT overlay
Implements full speech-to-speech pipeline (all 4 phases):

Backend (Phase 1):
- services/stt.py: lazy WhisperModel singleton, run_in_executor transcription
- services/tts.py: lazy KPipeline singleton, WAV synthesis at 24kHz/16-bit
- routes/voice.py: /api/voice/status, /voices, /transcribe, /synthesise
- config.py: VOICE_ENABLED, STT_BACKEND, STT_MODEL, TTS_BACKEND env vars
- app.py: load STT/TTS models at startup when VOICE_ENABLED=true
- llm.py: voice_mode + voice_speech_style params inject speak-naturally prefix
- generation_task.py: voice_mode passed through from chat route
- chat.py: "voice" conversation type allowed + excluded from retention cleanup
- pyproject.toml + Dockerfile: faster-whisper, kokoro, soundfile dependencies

Frontend (Phases 2–4):
- composables/useVoiceRecorder.ts: MediaRecorder PTT wrapper
- composables/useVoiceAudio.ts: AudioContext WAV playback wrapper
- BriefingView.vue: Listen button (TTS read-aloud), auto-TTS mode, mic PTT
- VoiceOverlay.vue: global floating PTT button; creates/reuses voice conv;
  full record→transcribe→stream→TTS flow; Space bar hold-to-talk via App.vue
- SettingsView.vue: Voice tab (status badge, speech style, voice/speed)
- App.vue: mounts VoiceOverlay; Space keydown/keyup fires voice:ptt-toggle
- api/client.ts: getVoiceStatus, getVoiceList, transcribeAudio, synthesiseSpeech

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 20:03:38 -04:00
bvandeusen 00643c778e security: fix 10 vulnerabilities from security audit
- SSRF: block private/internal URLs in image cache fetch
- SSRF: block private/internal URLs in RSS feed fetch (scheme guard)
- SSRF: block private/internal URLs in CalDAV URL setting
- Auth: require login for GET /api/images/<id> (was unauthenticated)
- Auth: restrict Ollama model pull/delete to admin users only
- Info disclosure: remove email from /api/users/search response
- OAuth: skip email-based account linking when email_verified is false
- Config: raise hard error on default SECRET_KEY when SECURE_COOKIES=true
- Rate limit: document proxy header requirement; add startup warning
- XSS: remove src/alt from global DOMPurify ADD_ATTR allowlist

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-29 00:37:13 -04:00
bvandeusen 697d99cc4d fix: pass temp_unit from API to WeatherCard so F/C label matches converted values 2026-03-28 13:21:30 -04:00
bvandeusen 164c55e845 fix: increase contrast on app version display in settings 2026-03-28 13:16:15 -04:00
bvandeusen ac90548823 feat: task management enhancements (cancelled status, recurrence, timestamps)
- Add 'cancelled' status to TaskStatus type, StatusBadge, TaskCard,
  TaskEditorView, TaskViewerView, TasksListView
- Add RecurrenceEditor component (none / interval / calendar rules)
- TaskEditorView: wire RecurrenceEditor, show started_at/completed_at
  timestamps read-only, include recurrence_rule in save payload
- TaskViewerView: show recurrence summary, timestamps in meta row
- tasks.ts: statusFilter/priorityFilter as arrays, add recurrence_rule
  to updateTask and createTask payloads

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:51:36 -04:00
bvandeusen 37e2420192 feat: add /news archive view with feed filter and pagination
- NewsView.vue: 90-day article archive, feed filter dropdown,
  load-more pagination, topic pills, thumbs up/down reactions
- Router: add /news route
- AppHeader: add News nav link (desktop + mobile)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:51:24 -04:00
bvandeusen f81c38df84 feat: redesign BriefingView into 3-column layout (weather · chat · news)
- Left column: weather loaded independently via /api/briefing/weather
- Center column: chat messages with input bar (unchanged behavior)
- Right column: news panel loaded from /api/briefing/news (last 2 days)
- Auto-scroll to bottom on mount and after streaming
- Background refresh also refreshes news panel
- Responsive: stacks to single column on narrow screens
- Fix TaskCard.vue to include 'cancelled' in status records

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:38:04 -04:00
bvandeusen 0a6e57e698 feat: add NewsItem type and getNewsItems() API client function
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-28 00:34:34 -04:00
bvandeusen 2a1644e571 feat: news story cards in briefing — backend embeds structured RSS items in metadata
Previously metadata only stored rss_item_ids (integers); the full item data
was discarded after LLM synthesis. Now rss_items (id, title, url, source,
snippet, published_at) is also stored so clients can render per-story cards
without additional API calls.

Web BriefingView: replaces bare reaction-row buttons with news cards showing
source, headline (linked), 2-line snippet, and 👍/👎 per card.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 00:08:21 -04:00
bvandeusen 699e525cb9 refactor: DRY pass on frontend — shared palette, composable, and tab loader
- Extract milestoneColor to utils/palette.ts; remove duplicate in HomeView + ProjectListView
- Create useBackgroundRefresh composable; wire into HomeView + BriefingView (removes manual setInterval/clearInterval boilerplate)
- Extract _loadTabContent() in SettingsView so watch and onMounted share one tab→loader mapping
- Move raw fetch() api-key calls to typed helpers in api/client.ts (listApiKeys, createApiKey, revokeApiKey)
- Drop unused onUnmounted import from BriefingView

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 22:43:59 -04:00
bvandeusen 2b2e5c666a fix: load all pre-selected settings tabs correctly on mount
The watch(activeTab) handler loads tab data on navigation, but not on
initial mount when localStorage restores a tab. Three more gaps:

- briefing: was inside the isAdmin guard — non-admin users who last
  visited the Briefing tab would see an empty form
- users: no onMounted equivalent — admin user list never loaded
- logs: no onMounted equivalent — admin log viewer never loaded

Moves briefing outside the admin guard and adds users/logs inside it.
2026-03-26 22:32:28 -04:00
bvandeusen 26a8fb5c51 fix: load API keys and MCP info on mount when tab is pre-selected
fetchApiKeys() and loadMcpInfo() were only wired to the activeTab watcher,
which fires on changes but not on initial mount. If localStorage had
'apikeys' as the last tab, both calls were skipped entirely — causing
an empty key list and no whl download button.
2026-03-26 22:24:37 -04:00
bvandeusen 916cfa50df fix: add missing onUnmounted import in BriefingView 2026-03-26 18:50:25 -04:00
bvandeusen 08d738ddfb feat: silent background polling for dashboard and briefing views
HomeView: setInterval every 90s refreshes events, orphan tasks/notes,
and hero next-up task. Never touches loading ref, so the page content
stays stable — only the data silently swaps when the fetch completes.
Skips when document.hidden or initial load is still in progress.

BriefingView: setInterval every 60s refetches today's messages, but
only when viewing today's conversation and not currently streaming.
Compares message count and last message content before updating to
avoid unnecessary re-renders.

Both timers are cleared on unmount.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 17:43:21 -04:00
bvandeusen a63e498067 fix: weather card spacing and sizing consistency
- Increase temp size to 2rem and add explicit font-size to condition
- Uniform 0.5rem spacing below current temp (was 0.35rem)
- Today row font-size explicit at 0.85rem
- Forecast gap 0.75rem (was 0.5rem), min-width 4.5rem (was 3.5rem)
- Padding-top on forecast strip 0.75rem to match overall rhythm

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-26 17:37:27 -04:00
bvandeusen aab478359b fix: decode HTML entities in wikilinks before re-escaping
marked encodes " to &quot; in text nodes, causing linkifyWikilinks to
double-escape it (& → &amp;) so the visible link text showed &quot;
instead of the actual character.

Decode marked's entities on the matched title/label before running
escapeHtmlAttr so the output is correct in both the href attribute
and the visible link text.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 22:40:25 -04:00