From d48612665bfe1926059a7815c664e07e880ad0de Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 3 Apr 2026 23:09:39 +0200 Subject: [PATCH] fix(toolbar): watch active ref for always-mounted views (AgentsView, ViewerView) Views mounted via v-if+CSS-hide never unmount, so onMounted/onUnmounted only fire once. When a RouterView (Tests) unmounts it clears _config, and the always-mounted view never restores it. Fix: provideToolbar accepts optional active ref and uses watch() instead of lifecycle hooks for those views. Co-Authored-By: Claude Sonnet 4.6 --- src/composables/useToolbar.ts | 28 ++++++++++++++++++---------- src/views/AgentsView.vue | 7 +++---- src/views/ViewerView.vue | 5 ++++- 3 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/composables/useToolbar.ts b/src/composables/useToolbar.ts index d92b548..3edab7c 100644 --- a/src/composables/useToolbar.ts +++ b/src/composables/useToolbar.ts @@ -9,7 +9,7 @@ * Views that don't provide → toolbar shows nothing (login, home). */ -import { onMounted, onUnmounted, ref, type Ref } from 'vue'; +import { onMounted, onUnmounted, ref, watch, type Ref } from 'vue'; // --- Types --- @@ -35,16 +35,24 @@ export interface ToolbarConfig { const _config = ref(null); let _mountVersion = 0; -export function provideToolbar(config: ToolbarConfig) { +/** + * Register toolbar config for the active view. + * + * Pass `active` for always-mounted views (v-if + CSS hide, e.g. AgentsView, ViewerView). + * Omit `active` for route-mounted views (RouterView) — uses onMounted/onUnmounted. + */ +export function provideToolbar(config: ToolbarConfig, active?: Ref) { let myVersion = 0; - onMounted(() => { - myVersion = ++_mountVersion; - _config.value = config; - }); - onUnmounted(() => { - // Only clear if no newer component has taken ownership since we mounted. - if (myVersion === _mountVersion) _config.value = null; - }); + + function activate() { myVersion = ++_mountVersion; _config.value = config; } + function deactivate() { if (myVersion === _mountVersion) _config.value = null; } + + if (active !== undefined) { + watch(active, (isActive) => { isActive ? activate() : deactivate(); }, { immediate: true }); + } else { + onMounted(activate); + onUnmounted(deactivate); + } } export function injectToolbar(): Ref { diff --git a/src/views/AgentsView.vue b/src/views/AgentsView.vue index 6921271..23add20 100644 --- a/src/views/AgentsView.vue +++ b/src/views/AgentsView.vue @@ -98,7 +98,8 @@ const { connected, send: wsSend } = ws; const { isLoggedIn } = auth; const { selectedAgent, selectedMode, filteredAgents, defaultAgent, allAgents } = agents; -// Toolbar — wrap chatStore state into ToolbarConnection shape +// Toolbar — always-mounted view: pass viewActive so config tracks route, not lifecycle +const viewActive = computed(() => agentsRoute.name === 'nyx'); const chatStore = useChatStore(); provideToolbar({ groups: ['quad-view', 'themes', 'panels'], @@ -119,7 +120,7 @@ provideToolbar({ Mode: selectedMode.value, }), }, -}); +}, viewActive); // Agent picker — disabled for now (auto-select default agent) // TODO: re-enable when multi-agent UX is designed @@ -160,8 +161,6 @@ watch( { immediate: true }, ); -const viewActive = computed(() => agentsRoute.name === 'nyx'); - // HUD state from ChatPane (for debug panels) const hudState = ref({ hudTree: [] as any[], hudVersion: 0, connected: false }); function onHudUpdate(payload: { hudTree: any[]; hudVersion: number; connected: boolean }) { diff --git a/src/views/ViewerView.vue b/src/views/ViewerView.vue index fb5f566..558c5a1 100644 --- a/src/views/ViewerView.vue +++ b/src/views/ViewerView.vue @@ -88,8 +88,11 @@ import { useViewer } from '../composables/useViewer'; import { provideToolbar } from '../composables/useToolbar'; import { computed } from 'vue'; -provideToolbar({ groups: ['themes'] }); +import { useRoute } from 'vue-router'; import { getApiBase } from '../utils/apiBase'; +const _route = useRoute(); +const _viewActive = computed(() => _route.name === 'viewer'); +provideToolbar({ groups: ['themes'] }, _viewActive); const { fstoken, viewerRoots, currentPath,