From 4aafbf6f6de55414be2bc074388127ad0f10c012 Mon Sep 17 00:00:00 2001 From: Till JS Date: Fri, 17 Apr 2026 15:17:30 +0200 Subject: [PATCH] fix(settings): react to anchor deep-links when already mounted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When clicking "KI-Einstellungen öffnen" from the companion chat while settings is already open on a different tab, the settings panel now correctly switches to the right tab and scrolls to the anchor. The workbench deep-link $effect dispatches a custom workbench:navigate-anchor event after opening/focusing the target panel. The settings ListView listens for both this event and native hashchange, then calls navigateToHash() to switch activeCategory and scroll. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/lib/modules/settings/ListView.svelte | 31 +++++++++++++++++-- .../apps/web/src/routes/(app)/+page.svelte | 10 ++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/apps/mana/apps/web/src/lib/modules/settings/ListView.svelte b/apps/mana/apps/web/src/lib/modules/settings/ListView.svelte index c5076c280..96fec0cba 100644 --- a/apps/mana/apps/web/src/lib/modules/settings/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/settings/ListView.svelte @@ -20,8 +20,7 @@ let activeCategory = $state('general'); - onMount(() => { - const hash = window.location.hash?.slice(1); + function navigateToHash(hash: string) { if (!hash) return; const cat = categories.find((c) => c.anchors.includes(hash)); if (cat) activeCategory = cat.id; @@ -29,6 +28,33 @@ const el = document.getElementById(hash); if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' }); }); + } + + onMount(() => { + const hash = window.location.hash?.slice(1); + if (hash) navigateToHash(hash); + }); + + // React to anchor navigations while already mounted (e.g. deep-link + // from companion chat "KI-Einstellungen öffnen" when settings is + // already open on a different tab). Listens for both native hashchange + // and the custom workbench:navigate-anchor event dispatched by the + // workbench deep-link handler. + $effect(() => { + const onHashChange = () => { + const hash = window.location.hash?.slice(1); + if (hash) navigateToHash(hash); + }; + const onAnchor = (e: Event) => { + const anchor = (e as CustomEvent<{ anchor: string }>).detail?.anchor; + if (anchor) navigateToHash(anchor); + }; + window.addEventListener('hashchange', onHashChange); + window.addEventListener('workbench:navigate-anchor', onAnchor); + return () => { + window.removeEventListener('hashchange', onHashChange); + window.removeEventListener('workbench:navigate-anchor', onAnchor); + }; }); function jumpTo(entry: SearchEntry) { @@ -68,6 +94,7 @@ gap: 1rem; height: 100%; overflow-y: auto; + overflow-x: hidden; } .settings-content { diff --git a/apps/mana/apps/web/src/routes/(app)/+page.svelte b/apps/mana/apps/web/src/routes/(app)/+page.svelte index 48cfc4e5c..c53104e00 100644 --- a/apps/mana/apps/web/src/routes/(app)/+page.svelte +++ b/apps/mana/apps/web/src/routes/(app)/+page.svelte @@ -89,15 +89,25 @@ $effect(() => { const target = $page.url.searchParams.get('app'); if (!target || !getApp(target)) return; + const hash = $page.url.hash?.slice(1) || ''; // Use queueMicrotask so we don't mutate state during the effect's first run queueMicrotask(async () => { const already = workbenchScenesStore.openApps.find((a) => a.appId === target); if (!already) await workbenchScenesStore.addApp(target); await tick(); scrollToPage(target); + // Clean the ?app= param but preserve the hash for the target panel const clean = new URL($page.url); clean.searchParams.delete('app'); history.replaceState({}, '', clean); + // Notify the target panel about the hash anchor (e.g. settings + // needs to switch to the right tab and scroll to the section) + if (hash) { + await tick(); + window.dispatchEvent( + new CustomEvent('workbench:navigate-anchor', { detail: { anchor: hash } }) + ); + } }); });