From 9c435cada089cd112d84493303e58e9eff5f9db2 Mon Sep 17 00:00:00 2001 From: thompson Date: Mon, 8 Jun 2026 21:44:18 +0100 Subject: [PATCH 1/3] feat: derive dashboard outline from visible sections --- .../__tests__/DashboardOutline.test.tsx | 326 ++++++++++++++++-- .../dashboard/components/ActiveDashboard.tsx | 225 ++++++------ .../dashboard/components/DashboardOutline.tsx | 88 ++--- .../chooseActiveDashboardOutlineItem.ts | 42 +++ .../collectDashboardOutline.ts | 80 +++++ .../components/dashboard-outline/index.ts | 4 + .../components/dashboard-outline/types.ts | 23 ++ .../dashboard-outline/useDashboardOutline.ts | 124 +++++++ 8 files changed, 712 insertions(+), 200 deletions(-) create mode 100644 frontend/apps/finance/src/features/dashboard/components/dashboard-outline/chooseActiveDashboardOutlineItem.ts create mode 100644 frontend/apps/finance/src/features/dashboard/components/dashboard-outline/collectDashboardOutline.ts create mode 100644 frontend/apps/finance/src/features/dashboard/components/dashboard-outline/index.ts create mode 100644 frontend/apps/finance/src/features/dashboard/components/dashboard-outline/types.ts create mode 100644 frontend/apps/finance/src/features/dashboard/components/dashboard-outline/useDashboardOutline.ts diff --git a/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx b/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx index 1ea9c64..af7b7e0 100644 --- a/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx +++ b/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx @@ -1,54 +1,314 @@ -import { describe, it, expect } from "vitest"; -import { render, screen } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { render, screen, waitFor } from "@testing-library/react"; +import { createRef, type ReactNode, type RefObject } from "react"; import { DashboardOutline } from "../components/DashboardOutline"; +import { + chooseActiveDashboardOutlineItem, + collectDashboardOutline, + toDashboardOutlineItems, + type DashboardOutlineItem, +} from "../components/dashboard-outline"; + +interface MockMutationObserverInstance { + callback: MutationCallback; + observe: ReturnType; + disconnect: ReturnType; +} + +interface MockIntersectionObserverInstance { + callback: IntersectionObserverCallback; + observe: ReturnType; + disconnect: ReturnType; +} + +const mutationObservers: MockMutationObserverInstance[] = []; +const intersectionObservers: MockIntersectionObserverInstance[] = []; + +class MockMutationObserver implements MutationObserver { + callback: MutationCallback; + observe = vi.fn(); + disconnect = vi.fn(); + takeRecords = vi.fn(() => []); + + constructor(callback: MutationCallback) { + this.callback = callback; + mutationObservers.push(this); + } +} + +class MockIntersectionObserver implements IntersectionObserver { + readonly root = null; + readonly rootMargin = ""; + readonly thresholds = []; + callback: IntersectionObserverCallback; + observe = vi.fn(); + unobserve = vi.fn(); + disconnect = vi.fn(); + takeRecords = vi.fn(() => []); + + constructor(callback: IntersectionObserverCallback) { + this.callback = callback; + intersectionObservers.push(this); + } +} + +beforeEach(() => { + mutationObservers.length = 0; + intersectionObservers.length = 0; + vi.stubGlobal("MutationObserver", MockMutationObserver); + vi.stubGlobal("IntersectionObserver", MockIntersectionObserver); +}); + +afterEach(() => { + vi.unstubAllGlobals(); +}); describe("DashboardOutline", () => { - it("renders a nav with Dashboard sections aria-label", () => { - render(); - expect(screen.getByRole("navigation", { name: "Dashboard sections" })).toBeInTheDocument(); + it("stays hidden until at least one outline item has been collected", () => { + const rootRef = createDashboardRoot(
); + + render(); + + expect(screen.queryByRole("navigation", { name: "Dashboard sections" })).not.toBeInTheDocument(); + }); + + it("collects visible outline nodes from the dashboard root in DOM order", async () => { + const rootRef = createDashboardRoot( + <> +
+
- {/* Recent Expenses or Empty State */} -
- - {data.recentExpenses.length === 0 && !readOnly ? ( - - - -

No expenses yet

-

- Start tracking your spending by logging your first expense for this - month. -

- -
-
- ) : data.recentExpenses.length > 0 ? ( - - ) : null} -
-
- {/* Dashboard Outline (TOC) - fixed positioned, renders on xl+ viewports */} - + ); } diff --git a/frontend/apps/finance/src/features/dashboard/components/DashboardOutline.tsx b/frontend/apps/finance/src/features/dashboard/components/DashboardOutline.tsx index bc338b6..aaf8b69 100644 --- a/frontend/apps/finance/src/features/dashboard/components/DashboardOutline.tsx +++ b/frontend/apps/finance/src/features/dashboard/components/DashboardOutline.tsx @@ -1,77 +1,53 @@ -const TOC_ITEMS = [ - { - label: "Summary", - href: "#summary", - children: [ - { label: "Budget Allocations", href: "#budget-allocations" }, - { label: "Spending Pace", href: "#spending-pace" }, - { label: "Historical Comparison", href: "#historical-comparison" }, - ], - }, - { - label: "Trends", - href: "#trends", - children: [ - { label: "Monthly Spending", href: "#trends" }, - { label: "Category Split", href: "#trends" }, - ], - }, - { - label: "Breakdown", - href: "#breakdown", - children: [ - { label: "Spending by Tag", href: "#breakdown" }, - { label: "Repeated Expenses", href: "#breakdown" }, - ], - }, - { label: "Cumulative Spending", href: "#cumulative-spending" }, - { label: "Recent Expenses", href: "#recent-expenses" }, -] as const; +import type { ReactNode } from "react"; +import { + useDashboardOutline, + type DashboardOutlineItem, + type DashboardOutlineProps, +} from "./dashboard-outline"; -interface TocItem { - label: string; - href: string; - children?: readonly { label: string; href: string }[]; -} +export function DashboardOutline({ rootRef }: DashboardOutlineProps) { + const { items, activeId } = useDashboardOutline(rootRef); + + if (items.length === 0) return null; -export function DashboardOutline() { return ( ); } -function TocEntry({ item }: { item: TocItem }) { - return ( -
  • +function renderOutlineItems( + items: DashboardOutlineItem[], + activeId: string | null, + isNested: boolean, +): ReactNode { + return items.map((item) => ( +
  • - {item.label} + {item.title} - {item.children && item.children.length > 0 && ( + {item.children.length > 0 && (
      - {item.children.map((child) => ( -
    • - - {child.label} - -
    • - ))} + {renderOutlineItems(item.children, activeId, true)}
    )}
  • - ); + )); +} + +function getLinkClassName(isActive: boolean, isNested: boolean): string { + const sizeClassName = isNested ? "py-0.5 text-[11px]" : "py-1"; + const activeClassName = isActive ? "text-foreground font-medium" : "hover:text-foreground"; + + return `block ${sizeClassName} ${activeClassName} transition-colors`; } diff --git a/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/chooseActiveDashboardOutlineItem.ts b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/chooseActiveDashboardOutlineItem.ts new file mode 100644 index 0000000..1c3ded7 --- /dev/null +++ b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/chooseActiveDashboardOutlineItem.ts @@ -0,0 +1,42 @@ +import type { DashboardOutlineItem } from "./types"; + +interface FlattenedOutlineItem { + id: string; + depth: number; + order: number; +} + +export function chooseActiveDashboardOutlineItem( + items: DashboardOutlineItem[], + activeIds: Set, +): string | null { + const activeItems = flattenOutlineItems(items).filter((item) => activeIds.has(item.id)); + if (activeItems.length === 0) return null; + + activeItems.sort((left, right) => { + if (left.depth !== right.depth) return right.depth - left.depth; + return left.order - right.order; + }); + + return activeItems[0].id; +} + +function flattenOutlineItems(items: DashboardOutlineItem[]): FlattenedOutlineItem[] { + const flattenedItems: FlattenedOutlineItem[] = []; + let order = 0; + + function visit(item: DashboardOutlineItem, depth: number) { + flattenedItems.push({ id: item.id, depth, order }); + order += 1; + + for (const child of item.children) { + visit(child, depth + 1); + } + } + + for (const item of items) { + visit(item, 0); + } + + return flattenedItems; +} diff --git a/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/collectDashboardOutline.ts b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/collectDashboardOutline.ts new file mode 100644 index 0000000..45c6ded --- /dev/null +++ b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/collectDashboardOutline.ts @@ -0,0 +1,80 @@ +import type { DashboardOutlineElement, DashboardOutlineItem } from "./types"; + +const OUTLINE_SELECTOR = "[id][data-outline-title]"; + +export function collectDashboardOutline(root: HTMLElement): DashboardOutlineElement[] { + const candidates = Array.from(root.querySelectorAll(OUTLINE_SELECTOR)); + const outlineElements = candidates.reduce((items, element) => { + if (!isElementVisible(element)) return items; + + const title = element.dataset.outlineTitle?.trim(); + if (!title) return items; + + items.push({ + id: element.id, + title, + element, + children: [], + }); + + return items; + }, []); + + return buildOutlineTree(outlineElements); +} + +export function toDashboardOutlineItems( + outlineElements: DashboardOutlineElement[], +): DashboardOutlineItem[] { + return outlineElements.map((item) => ({ + id: item.id, + title: item.title, + children: toDashboardOutlineItems(item.children), + })); +} + +function buildOutlineTree(items: DashboardOutlineElement[]): DashboardOutlineElement[] { + const roots: DashboardOutlineElement[] = []; + + for (const item of items) { + const parent = findNearestCollectedParent(item, items); + + if (parent) { + parent.children.push(item); + continue; + } + + roots.push(item); + } + + return roots; +} + +function findNearestCollectedParent( + item: DashboardOutlineElement, + items: DashboardOutlineElement[], +): DashboardOutlineElement | null { + const itemIndex = items.indexOf(item); + let nearestParent: DashboardOutlineElement | null = null; + + for (const candidate of items.slice(0, itemIndex)) { + if (!candidate.element.contains(item.element)) continue; + if (nearestParent && !nearestParent.element.contains(candidate.element)) continue; + + nearestParent = candidate; + } + + return nearestParent; +} + +function isElementVisible(element: HTMLElement): boolean { + if (element.hidden || element.getAttribute("aria-hidden") === "true") return false; + + const style = window.getComputedStyle(element); + if (style.display === "none" || style.visibility === "hidden") return false; + + const rect = element.getBoundingClientRect(); + if (rect.width > 0 || rect.height > 0) return true; + + return element.offsetParent !== null; +} diff --git a/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/index.ts b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/index.ts new file mode 100644 index 0000000..ebc2178 --- /dev/null +++ b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/index.ts @@ -0,0 +1,4 @@ +export { chooseActiveDashboardOutlineItem } from "./chooseActiveDashboardOutlineItem"; +export { collectDashboardOutline, toDashboardOutlineItems } from "./collectDashboardOutline"; +export { useDashboardOutline } from "./useDashboardOutline"; +export type { DashboardOutlineItem, DashboardOutlineProps } from "./types"; diff --git a/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/types.ts b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/types.ts new file mode 100644 index 0000000..119f845 --- /dev/null +++ b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/types.ts @@ -0,0 +1,23 @@ +import type { RefObject } from "react"; + +export interface DashboardOutlineItem { + id: string; + title: string; + children: DashboardOutlineItem[]; +} + +export interface DashboardOutlineState { + items: DashboardOutlineItem[]; + activeId: string | null; +} + +export interface DashboardOutlineElement { + id: string; + title: string; + element: HTMLElement; + children: DashboardOutlineElement[]; +} + +export interface DashboardOutlineProps { + rootRef: RefObject; +} diff --git a/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/useDashboardOutline.ts b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/useDashboardOutline.ts new file mode 100644 index 0000000..bdb2a97 --- /dev/null +++ b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/useDashboardOutline.ts @@ -0,0 +1,124 @@ +import { useCallback, useEffect, useRef, useState } from "react"; +import type { RefObject } from "react"; +import { chooseActiveDashboardOutlineItem } from "./chooseActiveDashboardOutlineItem"; +import { + collectDashboardOutline, + toDashboardOutlineItems, +} from "./collectDashboardOutline"; +import type { + DashboardOutlineElement, + DashboardOutlineItem, + DashboardOutlineState, +} from "./types"; + +const OBSERVED_ATTRIBUTES = [ + "id", + "data-outline-title", + "class", + "style", + "hidden", + "aria-hidden", +]; + +export function useDashboardOutline(rootRef: RefObject): DashboardOutlineState { + const [state, setState] = useState({ items: [], activeId: null }); + const outlineElementsRef = useRef([]); + const intersectingIdsRef = useRef>(new Set()); + + const updateActiveId = useCallback((items: DashboardOutlineItem[]) => { + const activeId = chooseActiveDashboardOutlineItem(items, intersectingIdsRef.current); + setState((previousState) => ({ ...previousState, activeId })); + }, []); + + const recollect = useCallback(() => { + const root = rootRef.current; + if (!root) { + outlineElementsRef.current = []; + intersectingIdsRef.current = new Set(); + setState({ items: [], activeId: null }); + return []; + } + + const outlineElements = collectDashboardOutline(root); + const items = toDashboardOutlineItems(outlineElements); + outlineElementsRef.current = outlineElements; + intersectingIdsRef.current = new Set( + Array.from(intersectingIdsRef.current).filter((id) => hasOutlineItem(items, id)), + ); + const activeId = chooseActiveDashboardOutlineItem(items, intersectingIdsRef.current); + setState({ items, activeId }); + + return flattenOutlineElements(outlineElements).map((item) => item.element); + }, [rootRef]); + + useEffect(() => { + const root = rootRef.current; + if (!root) return; + + recollect(); + + const mutationObserver = new MutationObserver(() => { + recollect(); + }); + + mutationObserver.observe(root, { + childList: true, + subtree: true, + attributes: true, + attributeFilter: OBSERVED_ATTRIBUTES, + }); + + return () => { + mutationObserver.disconnect(); + }; + }, [recollect, rootRef]); + + useEffect(() => { + const observedElements = flattenOutlineElements(outlineElementsRef.current).map((item) => item.element); + if (observedElements.length === 0) return; + + const intersectionObserver = new IntersectionObserver((entries) => { + for (const entry of entries) { + const target = entry.target; + if (!(target instanceof HTMLElement)) continue; + + if (entry.isIntersecting) { + intersectingIdsRef.current.add(target.id); + continue; + } + + intersectingIdsRef.current.delete(target.id); + } + + setState((previousState) => { + const activeId = chooseActiveDashboardOutlineItem( + previousState.items, + intersectingIdsRef.current, + ); + return { ...previousState, activeId }; + }); + }, { threshold: 0.1 }); + + for (const element of observedElements) { + intersectionObserver.observe(element); + } + + return () => { + intersectionObserver.disconnect(); + }; + }, [state.items]); + + useEffect(() => { + updateActiveId(state.items); + }, [state.items, updateActiveId]); + + return state; +} + +function flattenOutlineElements(items: DashboardOutlineElement[]): DashboardOutlineElement[] { + return items.flatMap((item) => [item, ...flattenOutlineElements(item.children)]); +} + +function hasOutlineItem(items: DashboardOutlineItem[], id: string): boolean { + return items.some((item) => item.id === id || hasOutlineItem(item.children, id)); +} From 48225da87b28dfcbf70337bf0d439d9caa4ea4da Mon Sep 17 00:00:00 2001 From: thompson Date: Mon, 8 Jun 2026 21:48:37 +0100 Subject: [PATCH 2/3] fix: omit empty dashboard outline sections --- .../__tests__/DashboardFeature.test.tsx | 16 +++ .../__tests__/DashboardOutline.test.tsx | 22 ++++ .../dashboard/components/ActiveDashboard.tsx | 124 ++++++++++-------- .../collectDashboardOutline.ts | 4 +- 4 files changed, 105 insertions(+), 61 deletions(-) diff --git a/frontend/apps/finance/src/features/dashboard/__tests__/DashboardFeature.test.tsx b/frontend/apps/finance/src/features/dashboard/__tests__/DashboardFeature.test.tsx index b79f33d..ca38614 100644 --- a/frontend/apps/finance/src/features/dashboard/__tests__/DashboardFeature.test.tsx +++ b/frontend/apps/finance/src/features/dashboard/__tests__/DashboardFeature.test.tsx @@ -254,6 +254,22 @@ describe("DashboardFeature", () => { expect(ctaLink).toHaveAttribute("href", "/expenses/new"); }); + it("does not expose outline metadata for dashboard sections without rendered content", async () => { + globalThis.fetch = createMockApi({ + "/api/finance/periods/current": { body: { period: testPeriod } }, + ...dashboardDataEmptyRoutes(), + }) as unknown as typeof fetch; + renderDashboard(); + + await waitFor(() => { + expect(screen.getByText("No expenses yet")).toBeInTheDocument(); + }); + + expect(document.querySelector('[data-outline-title="Historical Comparison"]')).toBeNull(); + expect(document.querySelector('[data-outline-title="Trends"]')).toBeNull(); + expect(document.querySelector('[data-outline-title="Cumulative Spending"]')).toBeNull(); + }); + it("renders repeated-expenses chart with frequency and recency context", async () => { const mockApi = createMockApi({ "/api/finance/periods/current": { body: { period: testPeriod } }, diff --git a/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx b/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx index af7b7e0..c259682 100644 --- a/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx +++ b/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx @@ -236,6 +236,28 @@ describe("collectDashboardOutline", () => { { id: "trends", title: "Trends", children: [] }, ]); }); + + it("excludes zero-size outline wrappers even when they have an offset parent", () => { + const root = document.createElement("div"); + const emptySection = appendVisibleElement(root, "empty", "Empty"); + emptySection.getBoundingClientRect = vi.fn(() => ({ + x: 0, + y: 0, + top: 0, + left: 0, + right: 0, + bottom: 0, + width: 0, + height: 0, + toJSON: () => ({}), + })); + Object.defineProperty(emptySection, "offsetParent", { + configurable: true, + value: document.body, + }); + + expect(toDashboardOutlineItems(collectDashboardOutline(root))).toEqual([]); + }); }); describe("chooseActiveDashboardOutlineItem", () => { diff --git a/frontend/apps/finance/src/features/dashboard/components/ActiveDashboard.tsx b/frontend/apps/finance/src/features/dashboard/components/ActiveDashboard.tsx index 7eb7055..950efe0 100644 --- a/frontend/apps/finance/src/features/dashboard/components/ActiveDashboard.tsx +++ b/frontend/apps/finance/src/features/dashboard/components/ActiveDashboard.tsx @@ -130,30 +130,36 @@ export function ActiveDashboard({ period, user, readOnly = false }: ActiveDashbo {/* Category Gauges */} -
    - - {data.summary && } - -
    - - {/* Spending Pace + Historical Comparison: side-by-side on desktop */} -
    -
    - - {data.summary && } + {data.summary && ( +
    + +
    -
    - - {data.comparison && ( - - )} - -
    -
    + )} + + {/* Spending Pace + Historical Comparison: side-by-side on desktop */} + {(data.summary || data.comparison) && ( +
    + {data.summary && ( +
    + + + +
    + )} + {data.comparison && ( +
    + + + +
    + )} +
    + )} {/* Upcoming Pro-rata */} @@ -168,18 +174,18 @@ export function ActiveDashboard({ period, user, readOnly = false }: ActiveDashbo {/* Charts: hidden on mobile per US-DASH-09 */}
    {/* Trends Section */} - + )} {/* Breakdown Section */}
    @@ -193,43 +199,45 @@ export function ActiveDashboard({ period, user, readOnly = false }: ActiveDashbo
    {/* Cumulative Spend Chart */} -
    - - {data.cumulativeData.length > 0 && ( + {data.cumulativeData.length > 0 && ( +
    + - )} - -
    +
    +
    + )}
    {/* Recent Expenses or Empty State */} -
    - - {data.recentExpenses.length === 0 && !readOnly ? ( - - - -

    No expenses yet

    -

    - Start tracking your spending by logging your first expense for this - month. -

    - -
    -
    - ) : data.recentExpenses.length > 0 ? ( - - ) : null} -
    -
    + {(data.recentExpenses.length > 0 || !readOnly) && ( +
    + + {data.recentExpenses.length === 0 ? ( + + + +

    No expenses yet

    +

    + Start tracking your spending by logging your first expense for this + month. +

    + +
    +
    + ) : ( + + )} +
    +
    + )} {/* Dashboard Outline (TOC) - fixed positioned, renders on xl+ viewports */} diff --git a/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/collectDashboardOutline.ts b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/collectDashboardOutline.ts index 45c6ded..2f77738 100644 --- a/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/collectDashboardOutline.ts +++ b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/collectDashboardOutline.ts @@ -74,7 +74,5 @@ function isElementVisible(element: HTMLElement): boolean { if (style.display === "none" || style.visibility === "hidden") return false; const rect = element.getBoundingClientRect(); - if (rect.width > 0 || rect.height > 0) return true; - - return element.offsetParent !== null; + return rect.width > 0 || rect.height > 0; } From 789e44f30432208ce9f95edd87a187de995b2829 Mon Sep 17 00:00:00 2001 From: thompson Date: Mon, 8 Jun 2026 21:52:23 +0100 Subject: [PATCH 3/3] test: cover dashboard outline attribute mutations --- .../__tests__/DashboardOutline.test.tsx | 20 +++++++++++++++++++ .../dashboard-outline/useDashboardOutline.ts | 9 --------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx b/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx index c259682..fc5129c 100644 --- a/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx +++ b/frontend/apps/finance/src/features/dashboard/__tests__/DashboardOutline.test.tsx @@ -162,6 +162,26 @@ describe("DashboardOutline", () => { expect(await screen.findByRole("link", { name: "Upcoming Pro-rata" })).toBeInTheDocument(); }); + it("updates outline nodes after observed attribute changes", async () => { + const rootRef = createDashboardRoot(
    ); + + render(); + await screen.findByRole("link", { name: "Summary" }); + + rootRef.current?.querySelector("#summary")?.setAttribute("data-outline-title", "Overview"); + mutationObservers[0].callback([], mutationObservers[0] as unknown as MutationObserver); + + expect(await screen.findByRole("link", { name: "Overview" })).toBeInTheDocument(); + expect(screen.queryByRole("link", { name: "Summary" })).not.toBeInTheDocument(); + + rootRef.current?.querySelector("#summary")?.setAttribute("hidden", ""); + mutationObservers[0].callback([], mutationObservers[0] as unknown as MutationObserver); + + await waitFor(() => { + expect(screen.queryByRole("navigation", { name: "Dashboard sections" })).not.toBeInTheDocument(); + }); + }); + it("preserves xl-only fixed positioning classes", async () => { const rootRef = createDashboardRoot(
    ); diff --git a/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/useDashboardOutline.ts b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/useDashboardOutline.ts index bdb2a97..e9ce262 100644 --- a/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/useDashboardOutline.ts +++ b/frontend/apps/finance/src/features/dashboard/components/dashboard-outline/useDashboardOutline.ts @@ -25,11 +25,6 @@ export function useDashboardOutline(rootRef: RefObject): Das const outlineElementsRef = useRef([]); const intersectingIdsRef = useRef>(new Set()); - const updateActiveId = useCallback((items: DashboardOutlineItem[]) => { - const activeId = chooseActiveDashboardOutlineItem(items, intersectingIdsRef.current); - setState((previousState) => ({ ...previousState, activeId })); - }, []); - const recollect = useCallback(() => { const root = rootRef.current; if (!root) { @@ -108,10 +103,6 @@ export function useDashboardOutline(rootRef: RefObject): Das }; }, [state.items]); - useEffect(() => { - updateActiveId(state.items); - }, [state.items, updateActiveId]); - return state; }