refactor: Phase 8 — backend deletion (chat / voice / push / journal / curator)

Mega-commit. Strips all server-side LLM machinery now that Phase 7 has
removed the corresponding UI surfaces and the MCP HTTP endpoint is the
sole assistant interface.

Deleted (services/):
  chat, generation_buffer, generation_log, generation_task, llm, tools/
  (entire package), stt, tts, voice_config, voice_library, push,
  journal_closeout, journal_pipeline, journal_prep, journal_scheduler,
  journal_search, curator, curator_scheduler, consolidation,
  tag_suggestions, research, weather, article_fetcher, pending_actions,
  moments, assist, wikipedia.

Deleted (routes/):
  chat, voice, push, journal, quick_capture, fable_mcp_dist.

Deleted (models/):
  conversation, generation_tool_log, push_subscription,
  pending_curator_action, moment, weather_cache.

Deleted (tests/):
  test_generation_log, test_journal_*, test_consolidation, test_lookup_tool,
  test_notes_consolidation_trigger, test_record_moment_guards,
  test_research_pipeline, test_tools_*, test_tool_use_fixes,
  test_voice_library, test_weather_service, test_calendar_tool_tz,
  test_wikipedia.

Deleted (top-level):
  fable-mcp/ (legacy standalone stdio package — wheel-build pipeline
  also removed from Dockerfile).

app.py:
  - blueprint registrations for the 6 deleted routes
  - startup hook trimmed: no more Ollama warmup, KV-cache priming,
    journal/curator schedulers, voice model loading
  - shutdown hook simplified
  - httpx import dropped (was for Ollama calls)

pyproject.toml:
  - removed deps: pywebpush, feedparser, html2text, trafilatura
  - removed [voice] extras entirely
  - description updated for the MCP-first architecture

Dockerfile:
  - removed faster-whisper / piper-tts install steps
  - removed bundled piper voice download stage
  - removed fable-mcp wheel build stage

Surviving-file edits:
  - services/auth.py: drop Conversation table claim on first-user setup
  - services/backup.py: drop conversation / push-subscription export+restore;
    v1/v2 restore now silently skip pre-pivot conversation data
  - services/notes.py: drop maybe_consolidate trigger on task done/cancelled;
    drop _maybe_trigger_project_summary (LLM auto-summary)
  - services/projects.py: drop generate_project_summary + backfill_project_summaries
    (both LLM-driven)
  - services/user_profile.py: drop append_observations / consolidate /
    clear_learned_data (curator-tied) and build_profile_context
    (was LLM system-prompt builder)
  - services/notifications.py: stub out _fire_push_notif (was send_push_notification)
  - services/event_scheduler.py: drop event-reminder push + chat-retention
    cleanup job; keep CalDAV pull-sync + reminders job (in-app)
  - services/diagnostics.py: _curator_busy() always False
  - routes/notes.py: drop /assist, /assist/stream, /suggest-tags endpoints
  - routes/tasks.py: drop /<id>/consolidate endpoint
  - routes/settings.py: drop /models, KV-cache-prime-on-save, journal-schedule
    timezone hook, and the SearXNG search-test endpoint; inline _is_private_url
    (was in services/llm.py)
  - routes/admin.py: drop /voice, /voice/reload endpoints
  - routes/profile.py: drop /consolidate, /observations (GET, DELETE)
  - models/__init__.py: drop the 6 dead model imports

Frontend cascade:
  - stores/push.ts: deleted entirely (no callers after Phase 7)
  - stores/settings.ts: drop checkVoiceStatus + voice-status state
  - views/SettingsView.vue: drop Locations section + journalConfig state
    (was tied to /api/journal/config); drop JournalConfig + journal/voice
    api/client imports
  - frontend/api/client.ts: orphaned voice/journal/profile-observation/
    fable-mcp-dist exports are left as dead but harmless (call them and
    they 404; type-check is clean).

Pre-existing v1 backups that contained conversations/messages still
restore — those tables are silently dropped from the import path.
Anyone pulling the new image with a populated database will need the
Phase 9 migration to drop the dead tables (coming next).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-27 17:47:18 -04:00
parent 8bec68abc0
commit 91bafb641f
123 changed files with 161 additions and 19312 deletions
+1 -149
View File
@@ -3,7 +3,7 @@ import { ref, computed, watch, onMounted } from "vue";
import { useSettingsStore } from "@/stores/settings";
import { useAuthStore } from "@/stores/auth";
import { useToastStore } from "@/stores/toast";
import { apiGet, apiPost, apiPut, apiDelete, listGroups, createGroup, deleteGroup, listGroupMembers, addGroupMember, removeGroupMember, searchUsers, listApiKeys, createApiKey as apiCreateApiKey, revokeApiKey as apiRevokeApiKey, getProfile, updateProfile, getJournalConfig, saveJournalConfig, geocodeAddress, type ApiKeyEntry, type GroupEntry, type GroupMember, type UserSearchResult, type UserProfile, type JournalConfig } from "@/api/client";
import { apiGet, apiPost, apiPut, apiDelete, listGroups, createGroup, deleteGroup, listGroupMembers, addGroupMember, removeGroupMember, searchUsers, listApiKeys, createApiKey as apiCreateApiKey, revokeApiKey as apiRevokeApiKey, getProfile, updateProfile, type ApiKeyEntry, type GroupEntry, type GroupMember, type UserSearchResult, type UserProfile } from "@/api/client";
import type { User } from "@/types/auth";
import PaginationBar from "@/components/PaginationBar.vue";
import TagInput from "@/components/TagInput.vue";
@@ -373,95 +373,6 @@ function toggleProfileWorkDay(day: string) {
function emptyTagsFetch(): Promise<string[]> { return Promise.resolve([]) }
// ── Journal config (locations, temp unit, prep schedule) ────────────────────
const journalConfig = ref<JournalConfig>({
prep_enabled: true,
prep_hour: 5,
prep_minute: 0,
day_rollover_hour: 4,
closeout_enabled: true,
locations: { home: { label: 'Home', address: '' }, work: { label: 'Work', address: '' } },
temp_unit: 'C',
})
const journalConfigSaving = ref(false)
const journalConfigSaved = ref(false)
const homeQuery = ref('')
const workQuery = ref('')
const homeGeoStatus = ref<'' | 'ok' | 'error' | 'pending'>('')
const workGeoStatus = ref<'' | 'ok' | 'error' | 'pending'>('')
const homeGeoMsg = ref('')
const workGeoMsg = ref('')
async function loadJournalConfig() {
try {
const cfg = await getJournalConfig()
journalConfig.value = {
prep_enabled: cfg.prep_enabled ?? true,
prep_hour: cfg.prep_hour ?? 5,
prep_minute: cfg.prep_minute ?? 0,
day_rollover_hour: cfg.day_rollover_hour ?? 4,
closeout_enabled: cfg.closeout_enabled ?? true,
morning_end_hour: cfg.morning_end_hour,
midday_end_hour: cfg.midday_end_hour,
locations: {
home: cfg.locations?.home ?? { label: 'Home', address: '' },
work: cfg.locations?.work ?? { label: 'Work', address: '' },
},
temp_unit: cfg.temp_unit ?? 'C',
}
homeQuery.value = cfg.locations?.home?.address ?? ''
workQuery.value = cfg.locations?.work?.address ?? ''
} catch { /* non-critical */ }
}
async function geocodeFor(which: 'home' | 'work') {
const query = (which === 'home' ? homeQuery.value : workQuery.value).trim()
const statusRef = which === 'home' ? homeGeoStatus : workGeoStatus
const msgRef = which === 'home' ? homeGeoMsg : workGeoMsg
const label = which === 'home' ? 'Home' : 'Work'
if (!query) {
// Clear location
if (!journalConfig.value.locations) journalConfig.value.locations = {}
journalConfig.value.locations[which] = { label, address: '' }
statusRef.value = ''
msgRef.value = ''
return
}
statusRef.value = 'pending'
msgRef.value = 'Looking up…'
try {
const result = await geocodeAddress(query)
if (!result) {
statusRef.value = 'error'
msgRef.value = `No match for "${query}"`
return
}
if (!journalConfig.value.locations) journalConfig.value.locations = {}
journalConfig.value.locations[which] = {
label,
address: query,
lat: result.lat,
lon: result.lon,
}
statusRef.value = 'ok'
msgRef.value = `Found: ${result.display_name}`
} catch {
statusRef.value = 'error'
msgRef.value = 'Geocoding failed'
}
}
async function saveJournalCfg() {
journalConfigSaving.value = true
journalConfigSaved.value = false
try {
await saveJournalConfig(journalConfig.value)
journalConfigSaved.value = true
setTimeout(() => { journalConfigSaved.value = false }, 2000)
} catch { toastStore.show('Failed to save journal settings', 'error') }
finally { journalConfigSaving.value = false }
}
onMounted(async () => {
try {
const v = await apiGet<{ version: string }>('/api/version')
@@ -486,7 +397,6 @@ onMounted(async () => {
await loadProfile();
// Load journal config (locations, temp unit; prep/closeout UI removed in Phase 7)
await loadJournalConfig();
// Load CalDAV settings
try {
@@ -1330,64 +1240,6 @@ function formatUserDate(iso: string): string {
</div>
</section>
<section class="settings-section full-width">
<h2>Locations</h2>
<p class="section-desc">Home and work locations are used by the journal's daily prep to fetch local weather. Place names are geocoded once on save.</p>
<div class="field">
<label>Home</label>
<div class="location-row">
<input
v-model="homeQuery"
type="text"
class="input"
placeholder="e.g. Brooklyn, NY"
@blur="geocodeFor('home')"
@keydown.enter.prevent="geocodeFor('home')"
/>
</div>
<p v-if="homeGeoStatus === 'ok'" class="geo-msg geo-ok">{{ homeGeoMsg }}</p>
<p v-else-if="homeGeoStatus === 'error'" class="geo-msg geo-error">{{ homeGeoMsg }}</p>
<p v-else-if="homeGeoStatus === 'pending'" class="geo-msg geo-pending">{{ homeGeoMsg }}</p>
</div>
<div class="field">
<label>Work</label>
<div class="location-row">
<input
v-model="workQuery"
type="text"
class="input"
placeholder="e.g. Manhattan, NY"
@blur="geocodeFor('work')"
@keydown.enter.prevent="geocodeFor('work')"
/>
</div>
<p v-if="workGeoStatus === 'ok'" class="geo-msg geo-ok">{{ workGeoMsg }}</p>
<p v-else-if="workGeoStatus === 'error'" class="geo-msg geo-error">{{ workGeoMsg }}</p>
<p v-else-if="workGeoStatus === 'pending'" class="geo-msg geo-pending">{{ workGeoMsg }}</p>
</div>
<div class="field">
<label>Temperature unit</label>
<div class="unit-toggle">
<button
type="button"
class="unit-btn"
:class="{ active: journalConfig.temp_unit === 'C' }"
@click="journalConfig.temp_unit = 'C'"
>Celsius</button>
<button
type="button"
class="unit-btn"
:class="{ active: journalConfig.temp_unit === 'F' }"
@click="journalConfig.temp_unit = 'F'"
>Fahrenheit</button>
</div>
</div>
<div class="actions">
<button class="btn-save" @click="saveJournalCfg" :disabled="journalConfigSaving">{{ journalConfigSaving ? 'Saving…' : 'Save' }}</button>
<span v-if="journalConfigSaved" class="saved-msg">Saved!</span>
</div>
</section>
</div>
<div v-show="activeTab === 'notifications'" class="settings-grid">