Three visual improvements for the briefing conversation:
1. Intermediate tool-call messages (empty content, briefing_intermediate:
true) now render as a compact dashed status row with per-tool pills
showing result counts, instead of an empty assistant bubble followed
by a stack of ToolCallCards. Click to expand the full cards.
2. Slot badge on non-intermediate briefing messages — "Full Briefing"
for compilation, "Morning/Midday/Afternoon Update" for slot
injections. Slot updates get a softer bubble treatment (transparent
background, muted border, smaller text) so the compilation stays
visually dominant.
3. Slot separator now triggers on briefing_slot metadata (not the
compilation-only rss_item_ids), and uses a look-behind so it only
fires when there's a prior slot message — no separator above the
first briefing of the day.
New component: BriefingToolStatusRow.vue handles the intermediate
pill row and delegates to ToolCallCard when expanded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace the hand-edit-JSON instructions in Settings → API Keys with a
tabbed UI (Claude Code / Claude Desktop / Other). The Claude Code tab
leads with the `claude mcp add` command, pre-filled with FABLE_URL and
the most recently generated API key, plus copy-to-clipboard buttons on
every snippet. Recommend `uv tool install` or `pipx install` over bare
`pip install` so fable-mcp reliably lands on PATH under PEP 668.
Also fix incorrect priority values in fable-mcp tool docstrings — the
enum is `none|low|medium|high`, not `low|normal|high`.
DRY pass: extract shared `copyToClipboard()` helper used by both
`copyApiKey` and the new snippet buttons; reuse existing
`btn btn-secondary btn-sm` for the copy buttons instead of a bespoke
class.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The standalone current-conditions div showed just a bare temperature with no
forecast context, making the weather panel look incomplete. Now the live
temperature from /weather/current is patched directly into the WeatherCard's
current_temp so it stays fresh without a second display block.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ProjectListView: add an overall task completion bar above the per-milestone
bars showing the percentage of all project tasks that are done.
ProjectView: milestones where every task is complete now start collapsed
by default, keeping the view clean for projects with many finished stages.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Exposes birthday, organization, address (person) and website, category (place)
from entity_meta in the knowledge API response; updates KnowledgeView cards
to display organization and category as visible meta chips/text.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When note type is 'person', replace the main TipTap editor with a contact card form
(Relationship, Birthday, Email, Phone, Organization, Address). The TipTap editor moves
to a collapsible 'Notes' section below the form, auto-expanded when existing body content
is present. Person fields are removed from the sidebar.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 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>
- 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>
- 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>
- 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>
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>
- 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>
- 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>
- 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>
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>
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>
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>
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>
- 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>
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>