From 9608b363ac897e81b929ec6a153937bf9483d316 Mon Sep 17 00:00:00 2001 From: Johannes Date: Wed, 1 Jul 2026 12:27:21 -0400 Subject: [PATCH] =?UTF-8?q?feat(guide):=20collapsible=20nav=20=E2=80=94=20?= =?UTF-8?q?Modules=20(per-app)=20/=20Platform=20/=20Technical=20sections?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Restructure reader sidebar into 3 collapsible top-level sections: Modules at top (each product app collapsible with its module list), then Platform and Technical. Auto-expands the section/app of the active module; search expands all. Co-Authored-By: Claude Opus 4.8 --- frontend/src/components/GuideReader.svelte | 110 +++++++++++++++++---- 1 file changed, 89 insertions(+), 21 deletions(-) diff --git a/frontend/src/components/GuideReader.svelte b/frontend/src/components/GuideReader.svelte index 4f51e7e..46bcce4 100644 --- a/frontend/src/components/GuideReader.svelte +++ b/frontend/src/components/GuideReader.svelte @@ -14,6 +14,15 @@ let activeModuleKey = ''; let activeTab = 0; let search = ''; + // collapse state: 'modules' | 'platform' | 'technical' | 'app:' + let open: Record = { modules: true }; + function toggle(key: string) { open = { ...open, [key]: !open[key] }; } + function expandFor(key: string) { + const app = key.split('.')[0]; + if (app === 'platform') open = { ...open, platform: true }; + else if (app === 'technical') open = { ...open, technical: true }; + else open = { ...open, modules: true, ['app:' + app]: true }; + } marked.setOptions({ gfm: true, breaks: false }); const render = (md: string): string => marked.parse(md, { async: false }) as string; @@ -38,13 +47,21 @@ })) .filter(a => a.modules.length > 0); + // group nav: product-app "Modules" section (all except platform/technical) + platform + technical + $: searching = !!search.trim(); + $: productApps = filtered.filter(a => a.app !== 'platform' && a.app !== 'technical'); + $: platformApp = filtered.find(a => a.app === 'platform') ?? null; + $: technicalApp = filtered.find(a => a.app === 'technical') ?? null; + const isOpen = (key: string) => searching || !!open[key]; + async function loadNav() { loadingNav = true; try { nav = await fetchNav(); - // auto-select the first module - const first = nav.find(a => a.modules.length)?.modules[0]; - if (first) selectModule(first.module); + // auto-select the first product module (or first available) + const first = (nav.find(a => a.app !== 'platform' && a.app !== 'technical' && a.modules.length) + ?? nav.find(a => a.modules.length))?.modules[0]; + if (first) { expandFor(first.module); selectModule(first.module); } } catch (e: any) { addToast('error', 'Failed to load guides', e?.response?.data?.message ?? e?.message ?? ''); } finally { @@ -54,6 +71,7 @@ async function selectModule(key: string) { if (key === activeModuleKey && module) return; + expandFor(key); activeModuleKey = key; loadingModule = true; module = null; @@ -88,21 +106,59 @@ {:else if filtered.length === 0}
No guides found.
{:else} - {#each filtered as app (app.app)} -