diff --git a/frontend/public/sw.js b/frontend/public/sw.js
index 5ce54d6..fd5182d 100644
--- a/frontend/public/sw.js
+++ b/frontend/public/sw.js
@@ -1,45 +1,11 @@
-// Service Worker for push notifications and PWA support
-self.addEventListener('push', event => {
- if (!event.data) return;
- const data = event.data.json();
- const notifUrl = data.url || '/';
+// Service Worker — PWA installability shell only.
+//
+// The push and notificationclick listeners were removed alongside the chat
+// generation pipeline (they only fired when the internal LLM finished a
+// response). The empty fetch handler is preserved as the installability
+// surface required for "Add to Home Screen" in some browsers; offline
+// caching is intentionally not implemented yet.
- event.waitUntil(
- clients.matchAll({ type: 'window', includeUncontrolled: true }).then(clientList => {
- // Suppress notification if the user already has the relevant page focused
- for (const client of clientList) {
- if (client.visibilityState === 'visible' && client.url.includes(notifUrl)) {
- return;
- }
- }
- return self.registration.showNotification(data.title || 'Scribe', {
- body: data.body || '',
- icon: '/favicon.ico',
- badge: '/favicon.ico',
- data: { url: notifUrl },
- });
- })
- );
-});
-
-self.addEventListener('notificationclick', event => {
- event.notification.close();
- event.waitUntil(
- clients.matchAll({ type: 'window', includeUncontrolled: true }).then(clientList => {
- const url = event.notification.data.url;
- for (const client of clientList) {
- if (client.url.includes(url) && 'focus' in client) {
- return client.focus();
- }
- }
- if (clients.openWindow) {
- return clients.openWindow(url);
- }
- })
- );
-});
-
-// PWA: serve cached assets for offline support (minimal)
-self.addEventListener('fetch', event => {
+self.addEventListener('fetch', () => {
// Pass-through — no offline caching in v1
});
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
index fd39b8e..4b0e8c5 100644
--- a/frontend/src/App.vue
+++ b/frontend/src/App.vue
@@ -5,43 +5,26 @@ import AppHeader from "@/components/AppHeader.vue";
import ToastNotification from "@/components/ToastNotification.vue";
import { useTheme } from "@/composables/useTheme";
import { useShortcuts } from "@/composables/useShortcuts";
-import { useOnnxPreloader } from "@/composables/useOnnxPreloader";
import { useAuthStore } from "@/stores/auth";
-import { useChatStore } from "@/stores/chat";
import { useSettingsStore } from "@/stores/settings";
import { apiGet, apiPut } from "@/api/client";
useTheme();
-const { schedulePreload: scheduleVadPreload } = useOnnxPreloader();
-scheduleVadPreload();
-
const router = useRouter();
const appVersion = ref("dev");
const authStore = useAuthStore();
-const chatStore = useChatStore();
const settingsStore = useSettingsStore();
const { showShortcuts, toggleShortcuts, closeShortcuts } = useShortcuts();
function startAppServices() {
- chatStore.startStatusPolling();
settingsStore.fetchSettings();
- settingsStore.checkVoiceStatus();
// Sync browser timezone to the server on every login/page load.
apiPut("/api/settings", { user_timezone: Intl.DateTimeFormat().resolvedOptions().timeZone }).catch(() => {});
- // Re-check voice status when the tab becomes visible again (model may
- // have finished loading while the user was away).
- document.addEventListener("visibilitychange", onVisibilityChange);
-}
-
-function onVisibilityChange() {
- if (document.visibilityState === "visible" && authStore.isAuthenticated) {
- settingsStore.checkVoiceStatus();
- }
}
function stopAppServices() {
- chatStore.stopStatusPolling();
+ // no-op for now; kept as a hook for future per-session teardown
}
function isInputActive(): boolean {
@@ -96,7 +79,6 @@ function onGlobalKeydown(e: KeyboardEvent) {
case "n": router.push("/notes"); break;
case "t": router.push("/"); break;
case "p": router.push("/projects"); break;
- case "c": router.push("/chat"); break;
case "g": router.push("/graph"); break;
case "l": router.push("/calendar"); break;
}
@@ -126,13 +108,6 @@ function onGlobalKeydown(e: KeyboardEvent) {
e.preventDefault();
document.dispatchEvent(new CustomEvent("shortcut:focus-search"));
break;
- case "c":
- if (router.currentRoute.value.name === "home") {
- document.dispatchEvent(new CustomEvent("shortcut:focus-chat"));
- } else {
- router.push("/chat");
- }
- break;
}
}
@@ -163,7 +138,6 @@ watch(
onUnmounted(() => {
document.removeEventListener("keydown", onGlobalKeydown);
- document.removeEventListener("visibilitychange", onVisibilityChange);
stopAppServices();
});
@@ -214,12 +188,6 @@ onUnmounted(() => {
p
Projects
-
- g
- +
- c
- Chat
-
g
+
@@ -267,23 +235,6 @@ onUnmounted(() => {
Edit current item (viewer)
-
-
Chat
-
- c
- Focus chat (home) / go to chat
-
-
- Enter
- Send message
-
-
- Shift
- +
- Enter
- New line
-
-
@@ -328,8 +279,6 @@ onUnmounted(() => {
min-height: 0;
overflow-y: auto;
}
-.app-content:has(.workspace-root),
-.app-content:has(.chat-page),
.app-content:has(.knowledge-root) {
overflow: hidden;
}
diff --git a/frontend/src/components/AppHeader.vue b/frontend/src/components/AppHeader.vue
index f11789e..553b0db 100644
--- a/frontend/src/components/AppHeader.vue
+++ b/frontend/src/components/AppHeader.vue
@@ -4,7 +4,6 @@ import { useRouter, useRoute } from "vue-router";
import { useTheme } from "@/composables/useTheme";
import { useShortcuts } from "@/composables/useShortcuts";
import { useAuthStore } from "@/stores/auth";
-import { useChatStore } from "@/stores/chat";
import AppLogo from "@/components/AppLogo.vue";
import NotificationBell from "@/components/NotificationBell.vue";
import { Sun, Moon, Settings } from "lucide-vue-next";
@@ -12,42 +11,13 @@ import { Sun, Moon, Settings } from "lucide-vue-next";
const { theme, toggleTheme } = useTheme();
const { toggleShortcuts } = useShortcuts();
const authStore = useAuthStore();
-const chatStore = useChatStore();
const router = useRouter();
const route = useRoute();
-const isChatActive = computed(() => route.path.startsWith("/chat"))
const isKnowledgeActive = computed(() => route.path === "/knowledge");
const mobileMenuOpen = ref(false);
-const statusShortLabel = computed(() => {
- if (chatStore.ollamaStatus === "unavailable") return "Offline";
- if (chatStore.ollamaStatus === "checking") return "Checking";
- if (chatStore.modelStatus === "not_found") return "No model";
- if (chatStore.modelStatus === "cold") return "Cold";
- if (chatStore.modelStatus === "loaded") return "Ready";
- return "Checking";
-});
-
-const statusLabel = computed(() => {
- if (chatStore.ollamaStatus === "unavailable") return "Ollama unavailable";
- if (chatStore.ollamaStatus === "checking") return "Checking...";
- if (chatStore.modelStatus === "not_found") return "Model not installed";
- if (chatStore.modelStatus === "cold") return "Model cold — first response may be slow";
- if (chatStore.modelStatus === "loaded") return "Model loaded · ready";
- return "Checking...";
-});
-
-const statusClass = computed(() => {
- if (chatStore.ollamaStatus === "unavailable") return "status-red";
- if (chatStore.ollamaStatus === "checking") return "status-gray";
- if (chatStore.modelStatus === "not_found") return "status-orange";
- if (chatStore.modelStatus === "cold") return "status-yellow";
- if (chatStore.modelStatus === "loaded") return "status-green";
- return "status-gray";
-});
-
function toggleMobileMenu() {
mobileMenuOpen.value = !mobileMenuOpen.value;
}
@@ -76,20 +46,13 @@ router.afterEach(() => {
Knowledge
- Chat
- Journal
Calendar
Projects
-
+
-
-
- {{ statusShortLabel }}
-
-
?
@@ -122,8 +85,6 @@ router.afterEach(() => {
@@ -656,41 +599,6 @@ onUnmounted(() => {
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
@@ -1294,71 +1202,4 @@ onUnmounted(() => {
height: 100%;
}
-/* ── Mini-chat ───────────────────────────────────────────── */
-.minichat {
- position: absolute;
- bottom: 0;
- left: var(--sidebar-width); /* align with content area past filter panel */
- right: 0;
- max-width: var(--page-max-width);
- margin-inline: auto;
- z-index: 20;
- display: flex;
- flex-direction: column;
- background: var(--color-bg-secondary);
- border-radius: 16px 16px 0 0;
- box-shadow: 0 -8px 32px rgba(0,0,0,0.35), 0 -1px 0 rgba(255,255,255,0.05);
- transition: height 0.2s ease;
-}
-.minichat--open {
- height: 500px;
-}
-.knowledge-root.graph-open .minichat {
- right: 500px;
- transition: right 0.2s ease;
-}
-.knowledge-root.graph-open.graph-expanded .minichat {
- right: min(960px, 60vw);
-}
-
-/* Header row (collapse / close buttons) */
-.minichat-header {
- display: flex;
- align-items: center;
- justify-content: space-between;
- padding: 8px 14px;
- border-bottom: 1px solid rgba(255,255,255,0.06);
- flex-shrink: 0;
-}
-.minichat-title {
- font-size: 0.82rem;
- font-weight: 500;
- color: var(--color-muted);
- text-transform: uppercase;
- letter-spacing: 0.06em;
-}
-.minichat-header-actions {
- display: flex;
- gap: 4px;
- align-items: center;
-}
-
-/* ChatPanel body — fills remaining space */
-.minichat-chat-panel {
- flex: 1;
- min-height: 0;
- overflow: hidden;
- display: flex;
- flex-direction: column;
-}
-.minichat-chat-panel :deep(.chat-body) {
- flex: 1;
- min-height: 0;
-}
-
-/* Standalone input row (closed / collapsed state) */
-.minichat-input-row {
- padding: 10px 14px;
- flex-shrink: 0;
-}
diff --git a/frontend/src/views/SettingsView.vue b/frontend/src/views/SettingsView.vue
index 95678bf..9f1a65c 100644
--- a/frontend/src/views/SettingsView.vue
+++ b/frontend/src/views/SettingsView.vue
@@ -82,7 +82,7 @@ const appVersion = ref('dev');
const restoreFileInput = ref(null);
// Migrate stored "admin" → "config"; unknown tabs fall back to "general"
-const VALID_TABS = new Set(["general", "account", "profile", "notifications", "integrations", "data", "voice", "apikeys", "config", "users", "logs", "groups"]);
+const VALID_TABS = new Set(["general", "account", "profile", "notifications", "integrations", "data", "apikeys", "config", "users", "logs", "groups"]);
const _stored = localStorage.getItem("settings_tab") ?? "general";
const activeTab = ref(VALID_TABS.has(_stored) ? (_stored === "admin" ? "config" : _stored) : "general");
@@ -92,7 +92,6 @@ function _loadTabContent(tab: string) {
else if (tab === "logs") loadLogsPanel();
else if (tab === "groups") loadGroupsPanel();
}
- if (tab === "voice") loadVoiceTab();
if (tab === "apikeys") { fetchApiKeys(); }
}
@@ -1482,7 +1481,7 @@ function formatUserDate(iso: string): string {
@@ -1946,109 +1830,6 @@ function formatUserDate(iso: string): string {
-
- Journal
- Controls when the daily journal prep generates and how the day is framed.
-
-
-
- Generate daily prep automatically
-
-
When enabled, the journal generates a morning briefing at the time below. When off, the journal still works — just without the auto-generated daily prep.
-
-
-
-
Prep generates at
-
-
- :
-
-
-
24-hour. Generates each morning at this local time.
-
-
-
Day rolls over at
-
-
- :00
-
-
Hour when the journal switches to a new day. Default 4am — late-night entries (1–3am) still count as the previous day.
-
-
-
- {{ journalConfigSaving ? 'Saving…' : 'Save' }}
- Saved!
-
-
-
-
- What the Assistant Has Learned
-
- The assistant observes patterns from your journal and chat conversations and builds a summary over time. The summary is included in the journal's system prompt so the daily prep can reference what it knows about you.
- {{ profile.observations_count }} raw observation{{ profile.observations_count !== 1 ? 's' : '' }} stored.
-
-
-
-
-
- Nightly closeout
- Extracts patterns from yesterday's journal at your day-rollover hour.
-
-
-
- {{ profile.learned_summary }}
- No learned summary yet. Observations accumulate from journal and chat conversations.
-
-
-
- {{ observationsExpanded ? '▾' : '▸' }} Recent observations ({{ profile.observations_count }})
-
-
-
Loading…
-
No observations yet.
-
-
-
{{ entry.date }}
-
{{ entry.bullets }}
-
-
-
-
-
-
-
- {{ consolidating ? 'Consolidating…' : 'Consolidate Now' }}
-
-
- {{ clearingObs ? 'Clearing…' : 'Reset Learned Data' }}
-
-
-
@@ -2080,103 +1861,6 @@ function formatUserDate(iso: string): string {
-
- Push Notifications
-
- Receive browser push notifications when a response is ready. Requires HTTPS.
-
-
- Push notifications are not supported in this browser or connection (requires HTTPS).
-
-
-
- Permission:
-
- {{ pushStore.permission === 'granted' ? 'Granted' : pushStore.permission === 'denied' ? 'Denied' : 'Not asked' }}
-
-
-
- Status:
-
- {{ pushStore.isSubscribed ? 'Subscribed' : 'Not subscribed' }}
-
-
- {{ pushStore.error }}
-
-
- {{ pushStore.loading ? 'Enabling...' : 'Enable Notifications' }}
-
-
- {{ pushStore.loading ? 'Disabling...' : 'Disable Notifications' }}
-
-
-
- Notifications are blocked. Allow them in your browser site settings to re-enable.
-
-
-
-
-
- If push notifications fail due to a corrupted or misformatted VAPID key, regenerate
- the key pair here. All existing subscriptions will be cleared — you will need to
- re-enable notifications in this browser afterwards.
-
-
-
- {{ vapidResetting ? 'Regenerating…' : 'Regenerate VAPID Keys' }}
-
-
-
- {{ vapidResetMsg }}
-
-
-
-
-
- Chat History
-
- Conversations older than this many days are automatically deleted when you load the chat.
- Set to 0 to keep conversations forever.
-
-
-
Retention period (days)
-
-
-
- {{ savingRetention ? 'Saving...' : 'Save' }}
-
-
-
-
-
-
- About
- Fabled Scribe {{ appVersion }}
-
-
-
@@ -2307,198 +1991,6 @@ function formatUserDate(iso: string): string {
-
-
-
- Voice
-
- Configure text-to-speech and speech-to-text for voice conversations.
- Requires VOICE_ENABLED=true on the server.
-
-
-
- Loading voice status…
- Voice status unavailable.
-
-
- {{ voiceStatus.enabled ? 'Enabled' : 'Disabled' }}
-
-
- STT {{ voiceStatus.stt ? 'ready' : 'loading…' }}
-
-
- TTS {{ voiceStatus.tts ? 'ready' : 'loading…' }}
-
-
- Model: {{ voiceStatus.stt_model }}
-
-
-
-
- Voice is disabled. An administrator can enable it under
- Admin → Config → Voice .
-
-
-
-
- Speech Style
- Controls how the assistant phrases spoken responses.
-
-
-
Style
-
-
-
-
- {{ opt.label }}
- {{ opt.hint }}
-
-
-
-
-
-
-
- Voice & Speed
- Choose the TTS voice and playback speed.
-
-
-
Voice
-
- af_heart (default)
- {{ v.label }}
-
-
Voice character and accent. Applies to all voice conversations.
-
-
-
-
- Speed: {{ voiceTtsSpeed.toFixed(1) }}×
-
-
-
- 0.7× (slow)
- 1.0× (normal)
- 1.3× (fast)
-
-
-
-
-
- {{ voicePreviewing ? 'Generating…' : '▶ Preview' }}
-
-
- {{ voiceSaved ? 'Saved ✓' : savingVoice ? 'Saving…' : 'Save Voice Settings' }}
-
-
-
-
-
-
-
-
-
-
- Download additional piper voices from the
- piper-voices catalog .
- Installed voices appear in every user's voice picker after a page reload.
-
-
-
-
-
-
- {{ voiceLibraryLoading ? 'Loading…' : 'Refresh' }}
-
-
-
-
- {{ voiceLibraryError }}
-
-
-
- Loading catalog…
-
-
-
-
-
- No voices match the filter.
-
-
-
-
-
diff --git a/frontend/src/views/WorkspaceView.vue b/frontend/src/views/WorkspaceView.vue
deleted file mode 100644
index ab33b27..0000000
--- a/frontend/src/views/WorkspaceView.vue
+++ /dev/null
@@ -1,240 +0,0 @@
-
-
-
-
-
-
-