- 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>
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.
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.
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>
npm install -g npm@latest corrupts npm's own module tree inside Alpine,
breaking subsequent installs. Use npm ci instead (faster, deterministic).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds a comprehensive instructions block to the FastMCP server covering
the data model hierarchy, valid enum values, tag format, integer-or-none
conventions, when to use fable_send_message vs direct CRUD tools, and
admin key requirements.
All tool docstrings expanded with full argument descriptions, valid
values, and return shape notes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The briefing was formatting event start_dt directly in UTC instead of
converting to the user's local timezone. Also, the day_start/day_end
query window was naive (UTC), so events at the edges of the user's day
could be missed or included incorrectly.
Now reads user_timezone setting, uses it for today's date boundary and
for converting each event's start_dt before formatting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
marked encodes " to " in text nodes, causing linkifyWikilinks to
double-escape it (& → &) so the visible link text showed "
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>
Browser timezone is now synced to user_settings["user_timezone"] on
every login/page load (App.vue). The briefing scheduler and LLM context
both read from this single source, falling back to the legacy
briefing_config.timezone for existing users during migration.
- App.vue: PUT /api/settings with browser IANA timezone on startAppServices
- routes/chat.py: fall back to stored user_timezone when not sent in request
- briefing_scheduler: read user_timezone setting; briefing_config.timezone
kept as fallback only
- routes/briefing.py: pass tz_override from user_timezone to live-patched scheduler
- Remove timezone field from BriefingConfig interface and all briefing UI
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Frontend sends user_timezone (IANA, from Intl.DateTimeFormat) with
every message POST; threaded through route → generation_task → build_context
- System prompt now tells the LLM the user's timezone so it creates
events with the correct UTC offset (e.g. 15:00+01:00 not 15:00Z)
- Calendar tool guidance updated to require UTC offset in all event
datetimes
- EventSlideOver: dateFromIso/timeFromIso now use JS Date to convert
stored UTC times to local time for display; toIso includes local
timezone offset when saving so the correct UTC time is stored
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ToolCallCard: event list items replaced with rich clickable cards (color dot,
title, time, location); clicking opens EventSlideOver for edit/delete; single
create/update events in header are also clickable; updated all event types to
use start_dt/end_dt fields from internal store
- HomeView: new upcoming events widget shows today + next 7 days as a card grid
above the hero project; clicking any card opens EventSlideOver inline
- briefing_pipeline: _gather_internal now queries the internal events store for
today's events; CalDAV events are still appended (deduped) if configured
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The local `from datetime import datetime, timezone as _tz` inside
execute_tool() shadowed the module-level datetime import for the entire
function scope, causing UnboundLocalError in all calendar tool handlers
(list_events, create_event, update_event). Fixed by importing only
timezone as _tz — datetime is already available at module level.
Also removes the now-unnecessary noqa: F823 suppression.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>