diff --git a/internal/webapp/frontend/e2e/scope.spec.ts b/internal/webapp/frontend/e2e/scope.spec.ts index 88d2aea..145c42b 100644 --- a/internal/webapp/frontend/e2e/scope.spec.ts +++ b/internal/webapp/frontend/e2e/scope.spec.ts @@ -9,6 +9,9 @@ test("insights via ⋯ scopes to the open file", async ({ page }) => { await page.click("#more-menu .more-item:has-text('Insights')"); await expect(page).toHaveURL(`/${pid}/insights/notes/readme.md`); await expect(page.locator(".in-title .in-scope")).toContainText("notes/readme.md"); + // The subject stays selected in the tree; Dashboard does NOT light up. + await expect(page.locator('#tree .row[data-path="notes/readme.md"]')).toHaveClass(/active/); + await expect(page.locator("#nav-dashboard")).not.toHaveClass(/active/); }); test("insights via ⋯ scopes to the selected folder", async ({ page }) => { @@ -19,4 +22,14 @@ test("insights via ⋯ scopes to the selected folder", async ({ page }) => { await page.click("#more-menu .more-item:has-text('Insights')"); await expect(page).toHaveURL(`/${pid}/insights/notes`); await expect(page.locator(".in-title .in-scope")).toContainText("notes"); + await expect(page.locator('#tree .row[data-path="notes"]')).toHaveClass(/active/); + await expect(page.locator("#nav-dashboard")).not.toHaveClass(/active/); +}); + +test("root Dashboard still lights the menu, not the tree", async ({ page }) => { + await login(page); + await wikiId(page); + await page.click("#nav-dashboard"); + await expect(page.locator("#nav-dashboard")).toHaveClass(/active/); + await expect(page.locator("#tree .row.active")).toHaveCount(0); }); diff --git a/internal/webapp/frontend/src/apps/Browser.tsx b/internal/webapp/frontend/src/apps/Browser.tsx index 3ffce54..3315eee 100644 --- a/internal/webapp/frontend/src/apps/Browser.tsx +++ b/internal/webapp/frontend/src/apps/Browser.tsx @@ -59,6 +59,9 @@ export default function Browser(props: { }, [insightsOpen, apiBase, qc]); const path = route.path; + // On scoped view routes (/insights/
, /history/
) the subject of the
+ // page is the target — the tree highlights it, not a menu item.
+ const treePath = path || (route.view === "insights" || route.view === "history" ? route.viewTarget || "" : "");
const isDir = !!path && dirIndex.has(path);
// A file only counts as one when the tree actually contains it — a
// missing path gets the not-found view, not a broken file view.
@@ -78,18 +81,19 @@ export default function Browser(props: {
if (rootDirs.length === 1) setExpanded((s) => new Set(s).add(rootDirs[0].path));
}, [tree]);
useEffect(() => {
- // Opening any path (tree click, palette, wikilink, deep link) unfolds
- // the way to it; a selected folder itself opens too.
- if (!path || !loaded) return;
+ // Opening any path (tree click, palette, wikilink, deep link — or a
+ // scoped insights/history view of it) unfolds the way to it; a selected
+ // folder itself opens too.
+ if (!treePath || !loaded) return;
setExpanded((s) => {
const next = new Set(s);
- for (const a of ancestorsOf(path)) next.add(a);
- if (dirIndex.has(path)) next.add(path);
+ for (const a of ancestorsOf(treePath)) next.add(a);
+ if (dirIndex.has(treePath)) next.add(treePath);
return next;
});
- const row = document.querySelector(`#tree .row[data-path="${CSS.escape(path)}"]`);
+ const row = document.querySelector(`#tree .row[data-path="${CSS.escape(treePath)}"]`);
if (row) row.scrollIntoView({ block: "nearest" });
- }, [path, loaded, dirIndex]);
+ }, [treePath, loaded, dirIndex]);
const onToggle = useCallback((p: string) => {
setExpanded((s) => {
const next = new Set(s);
@@ -420,7 +424,7 @@ export default function Browser(props: {
root={tree}
expanded={expanded}
onToggle={onToggle}
- currentPath={path}
+ currentPath={treePath}
listingShowing={listingShowing}
onOpen={openPath}
/>
diff --git a/internal/webapp/frontend/src/apps/HubApp.tsx b/internal/webapp/frontend/src/apps/HubApp.tsx
index 1a311f7..8cbc50d 100644
--- a/internal/webapp/frontend/src/apps/HubApp.tsx
+++ b/internal/webapp/frontend/src/apps/HubApp.tsx
@@ -192,13 +192,16 @@ export default function HubApp({ config }: { config: ServerConfig }) {
projects={projects}
currentId={current.id}
menu={{
+ // Scoped views (/insights/d(L,V))Gd(L,V))G