- Add trafilatura + html2text to dependencies
- Replace custom HTMLStripper with html2text for RSS feed content
- Fetch full article text via httpx + trafilatura after each new item is stored;
falls back to RSS-provided content if fetch/extraction fails
- Raise CONTENT_MAX_CHARS from 2000 to 50000 (TEXT column, no migration needed)
- Re-embed items with full article content once enrichment completes
- Startup backfill enriches existing items with short content (<1000 chars)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
feedparser returns HTML in content/summary fields for many feeds.
Raw tags were being stored in the DB and passed to the LLM/embeddings.
Added a stdlib HTMLParser-based stripper in extract_item() — block elements
become newlines, script/style content is dropped, plain text passes through.
No new dependencies required.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
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>
Replace two-pass structured LLM synthesis (## Your Day / ## The World
sections with bullets and formatted news cards) with a single
conversational pass. The new prompt instructs the model to write 3-5
flowing sentences covering weather, today's tasks/events, and 1-2 news
highlights — no markdown, no headers, no lists. Full news detail stays
in the right panel; weather detail stays in the weather card.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Prevents models from sitting in VRAM indefinitely. Applies to both
streaming chat calls and the non-streaming generate_completion path,
as well as the startup warm-up request.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
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>
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>
Reflects all changes since initial 0.1.0 release: RSS tools, task log
content/body fix, project and milestone status on create, and various
other fixes. Auto-bump hook will handle patch increments from here.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
create_project and create_milestone hardcoded status="active" and
ignored any value passed by the MCP or API callers. Route, service,
and model construction now all thread the status field through.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The MCP tool was sending {"body": ...} but the task logs API route
expects {"content": ...}, causing 400 errors.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- scripts/bump_fable_mcp_version.sh: increments patch in pyproject.toml and stages it
- scripts/pre_commit_fable_mcp.sh: PreToolUse Bash hook — fires before git commits,
bumps version if fable-mcp files (other than pyproject.toml itself) are staged
- .claude/settings.json: registers the PreToolUse hook
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
Add _build_briefing_article_context() helper to llm.py that reads
rss_item_ids from briefing message metadata and injects article content
into the system prompt. Pass conv_id through build_context() and
generation_task.py.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
TaskStatus enum was missing 'cancelled' — the LLM tried to use it and
hit TaskStatus("cancelled") raising ValueError → 500. Added the value,
a migration to extend the task_status Postgres enum, and proper 400
validation guards on both create and update task routes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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>
The MCP fable_update_task tool calls PATCH /api/tasks/{id} but the route
only declared PUT. Adding PATCH to the same handler fixes the 405.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add parse_pagination() to routes/utils.py; replace 6 duplicate limit/offset extractions in notes, tasks, chat, projects, milestones routes
- Extract _enrich_shares() in sharing.py; eliminates identical 12-line loop in list_project_shares and list_note_shares
- Extract _deduplicate_by_permission() in sharing.py; eliminates identical deduplication blocks in list_shared_with_me for projects and notes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>