Drift-audit Group 6 + Group 5 #6 (enum-extension / status drift):
- ProjectStatus gains 'paused' — routes and frontend already treated it as
first-class, but the enum (the source of truth) omitted it and the error
strings lied. A future CHECK derived from the enum would have rejected
existing paused rows.
- create_project/update_project now validate status via ProjectStatus at the
service layer (canonical gate; notes.status has no DB CHECK), so the MCP
create/update_project path can't persist a typo'd status. MCP docstrings
realigned to the 4-value domain; route error strings corrected.
- get_milestone_progress: cancelled tasks are excluded from the percent
denominator (and now reported in status_counts), so a milestone whose only
open task was cancelled reaches 100% instead of stalling below it.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drift-audit Group 3 (soft-delete lifecycle gaps). Trashed rows were
leaking into reads and being mutated/resurrected by writes:
- update SELECTs now exclude trashed rows: update_milestone,
update_project, update_event, and get_milestone_in_project (the latter
backs all four milestone routes). Mutating a trashed row silently
persisted and reappeared on restore.
- MCP get_recent (notes/projects/events) and list_tags now filter
deleted_at IS NULL, so trashed items stop surfacing in the agent's
bootstrap context and tag counts.
- convert_task_to_note clears recurrence_rule + recurrence_next_spawn_at
so a demoted note can't spawn children via the (now-live) sweep.
- caldav pull skips locally-trashed events (by caldav_uid) instead of
resurrecting them via update or creating a duplicate live copy.
- trash _cascade now stamps the FULL sub-task subtree (iterative descent),
not just direct children, so deeply nested sub-tasks restore as one
batch. Test updated for the new descent query.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
services/projects.py:get_project_summary built task_counts dynamically
from a GROUP BY query, so a project with no done tasks would omit the
'done' key entirely. Frontend's TypeScript interface declares all three
lifecycle keys as required, and ProjectView.vue summed them to render
the Tasks tab counter — undefined + N = NaN.
Two fixes:
1. Backend: initialise task_counts with {todo: 0, in_progress: 0,
done: 0} so the service returns the contract its consumers expect.
Catches the same problem for HomeView's project widget and any
other consumer.
2. Frontend: defensive ?? 0 on the tab-counter sum, so the existing
deploy renders correctly even before the backend rolls.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Two small hardening fixes from the mistral-nemo testing round:
1. stream_chat / stream_chat_with_tools now read the Ollama response
body and log it before raising on non-2xx. Previously all we saw
was 'HTTP 400 Bad Request' — the gemma3-no-tools failure would
have been diagnosed in one step if we'd been logging the body,
which says e.g. 'model does not support tools'.
2. backfill_project_summaries() now also targets summaries stamped
before 2026-04-12 (the gemma3:4b cutover). The remaining projects
still carrying the broken qwen2.5:3b output (token repetition,
hallucinated topics) will regenerate on next startup on the
better model.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Project auto-summaries were using the 3B background model, but the
task — synthesizing a coherent paragraph over ~10 notes — is well past
what 3B can do reliably. Evidence on dev: "doging conversation
hygiene", "MCPview). MCP).", trailing stray quotes, and hallucinated
topics ("AI regulation").
Route through the user's default chat model instead. Project summary
regeneration is rare (only when a project changes) so the KV cache
eviction cost on the main model is negligible.
Title generation, tag suggestions, and RSS classification continue to
use the background model — those tasks are within what 3B handles.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
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>
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>