Skip to content

feat(dashboard): add Table of Contents right rail with bulk collapse/expand#2350

Merged
kodiakhq[bot] merged 4 commits into
mainfrom
dashboard-toc-right-rail
May 28, 2026
Merged

feat(dashboard): add Table of Contents right rail with bulk collapse/expand#2350
kodiakhq[bot] merged 4 commits into
mainfrom
dashboard-toc-right-rail

Conversation

@teeohhem
Copy link
Copy Markdown
Contributor

@teeohhem teeohhem commented May 27, 2026

Summary

Adds a toggleable right-rail Table of Contents to the dashboard page, plus "Collapse all sections" and "Expand all sections" actions — all surfaced from a new "View" section in the existing dashboard menu. The TOC lists every section, click-to-jump scrolls into view and auto-expands a collapsed target, and rail visibility persists per user via localStorage (off by default). Bulk collapse/expand uses the same per-viewer URL state as the existing single-section toggle, so it's shareable via link and does not mutate the dashboard's stored defaults.

Screenshots or video

image image
Node-js-Runtime-Metrics-.-ClickStack.mp4

How to test on Vercel preview

Preview routes: /dashboards

Steps:

  1. Open any saved dashboard that has at least two sections (groups). If none exist, click Add → New Group twice to create them.
  2. Click the dashboard's three-dot menu (top-right) and select Show table of contents under the View label. Verify a right-rail titled "Sections" appears listing every section.
  3. Click a section name in the rail; verify the main view scrolls to that section.
  4. In the same menu, click Collapse all sections, then Expand all sections; verify every collapsible section toggles and that the URL gains a collapsed= or expanded= parameter.
  5. Reload the page; verify the TOC rail is still visible (localStorage), then click the rail's × button and reload again to confirm it stays hidden.

References

  • Linear Issue:
  • Related PRs:

…expand

Adds a toggleable right-rail Table of Contents to the dashboard page plus
"Collapse all sections" / "Expand all sections" actions, all surfaced from
a new "View" section in the existing dashboard menu.

- TOC visibility is persisted per-user via localStorage (off by default).
- Bulk collapse uses the same per-viewer URL state as single-section
  toggling — shareable via link, does not mutate the dashboard's stored
  defaults.
- Clicking a TOC entry scrolls the section into view, auto-expanding it
  first if collapsed.
- Local (unsaved) dashboards keep the existing behaviour of having no
  3-dot menu.

Includes unit tests for the new DashboardTableOfContents component with
100% coverage.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 27, 2026

🦋 Changeset detected

Latest commit: 39f345f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
@hyperdx/app Patch
@hyperdx/api Patch
@hyperdx/otel-collector Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@vercel
Copy link
Copy Markdown

vercel Bot commented May 27, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview, Comment May 28, 2026 3:19pm
hyperdx-storybook Error Error May 28, 2026 3:19pm

Request Review

@github-actions github-actions Bot added the review/tier-3 Standard — full human review required label May 27, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

🟡 Tier 3 — Standard

Introduces new logic, modifies core functionality, or touches areas with non-trivial risk.

Why this tier:

  • Diff size: 462 production lines changed (Tier 2 max: < 250)

Review process: Full human review — logic, architecture, edge cases.
SLA: First-pass feedback within 1 business day.

Stats
  • Production files changed: 3
  • Production lines changed: 462 (+ 217 in test files, excluded from tier calculation)
  • Branch: dashboard-toc-right-rail
  • Author: teeohhem

To override this classification, remove the review/tier-3 label and apply a different review/tier-* label. Manual overrides are preserved on subsequent pushes.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

E2E Test Results

All tests passed • 192 passed • 3 skipped • 1293s

Status Count
✅ Passed 192
❌ Failed 0
⚠️ Flaky 3
⏭️ Skipped 3

Tests ran across 4 shards in parallel.

View full report →

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 27, 2026

Deep Review

Scope: 5 files, +576/-116 (1 new component, 1 new test file, DBDashboardPage layout/menu wiring, DashboardContainer anchor id, changeset).
Mode: report-only.

🟡 P2 -- recommended

  • packages/app/src/components/DashboardContainer.tsx:243 -- The DOM id is built inline as `dashboard-container-${container.id}` while DashboardTableOfContents exports and consumes TOC_CONTAINER_ANCHOR_ID(id) for the same lookup, so the click-to-jump contract has two unrelated sources of truth and silently breaks if either prefix is renamed.
    • Fix: Import TOC_CONTAINER_ANCHOR_ID from DashboardTableOfContents and use id={TOC_CONTAINER_ANCHOR_ID(container.id)} so the anchor string is defined once.
    • maintainability, kieran-typescript
  • packages/app/src/components/__tests__/DashboardTableOfContents.test.tsx:107 -- beforeEach assigns Element.prototype.scrollIntoView = scrollIntoViewMock directly on the jsdom prototype but afterEach only restores the rAF spy, leaving a stale jest.fn() on every Element for subsequent test files in the same Jest worker.
    • Fix: Capture the original descriptor (or use jest.spyOn(Element.prototype, 'scrollIntoView')) and restore it in afterEach so the prototype mutation does not leak across files.
    • testing, julik-frontend-races
  • packages/app/src/DBDashboardPage.tsx:2006 -- The new handleCollapseAll/handleExpandAll URL writes, the containers.length > 0 "View" menu guard, and the tocVisible resize-dispatch effect are all promised behaviors in the changeset but have no test coverage; the DBDashboardPage has no existing test file, so regressions in the bulk-toggle URL contract or the on-toggle RGL re-measure will land without CI signal.
    • Fix: Add tests that exercise the View menu items, assert the resulting collapsed= / expanded= URL params for both collapsible and non-collapsible containers, and verify a window resize event fires when tocVisible flips.
    • testing, maintainability
  • packages/app/src/components/DashboardTableOfContents.tsx:91 -- The label is firstTab?.title ?? container.title, so a container whose viewer has selected a non-first tab via the tabs URL param still appears in the TOC under the first tab's title -- diverging from the visible header and from what the user just selected.
    • Fix: Either always label with container.title (stable) or resolve the active tab via getActiveTabId(container) and use that tab's title, so the TOC and the rendered header agree.
    • correctness
🔵 P3 nitpicks (6)
  • packages/app/src/components/__tests__/DashboardTableOfContents.test.tsx:116 -- return 0 as unknown as number is a double cast on a value that is already number, contradicting the repo's "avoid as casts" rule.
    • Fix: Replace the as unknown as number with a plain return 0.
  • packages/app/src/DBDashboardPage.tsx:2006 -- handleCollapseAll / handleExpandAll unconditionally call setUrlExpandedIds(null) / setUrlCollapsedIds(null), discarding any pre-existing URL state for non-collapsible containers that the bulk action did not actually touch.
    • Fix: Filter the opposite set rather than nulling it, or add a brief comment stating that resetting the opposite side is the intended "bulk expresses full intent" semantics.
  • packages/app/src/DBDashboardPage.tsx:1643 -- The useEffect keyed on tocVisible runs once on mount and again when useLocalStorage hydrates the persisted value, dispatching a synthetic window resize even though nothing visually changed.
    • Fix: Guard the effect with a didMountRef (or a previous-value ref) so the dispatch only fires on real tocVisible transitions.
    • correctness, julik-frontend-races
  • packages/app/src/components/DashboardTableOfContents.tsx:38 -- A single requestAnimationFrame between onToggleCollapse and scrollIntoView may fire before nuqs's URL flush + React's expand re-render commits, so the deferred scroll can land against the still-collapsed layout.
    • Fix: Set a pendingScrollTargetId state and call scrollIntoView from an effect that watches isContainerCollapsed(target) === false, so the scroll runs only after the expansion has committed.
  • packages/app/src/DBDashboardPage.tsx:1644 -- Dispatching a global window resize event re-fires every chart, table autosizer, virtual list, and per-container WidthProvider on the page for what is a single-column width change.
    • Fix: Replace the synthetic resize with a ResizeObserver on the grid column wrapper that drives the grid widths directly, scoping the work to the grids that actually changed.
  • packages/app/src/components/__tests__/DashboardTableOfContents.test.tsx:148 -- The "expand before scroll" test asserts both onToggleCollapse and scrollIntoView were called but never that toggle preceded scroll, so a regression that scrolls first would still pass.
    • Fix: Use mock.invocationCallOrder (or record a per-call log) to assert onToggleCollapse fires before the rAF-scheduled scrollIntoView.

Reviewers (9): correctness, testing, maintainability, project-standards, agent-native, learnings-researcher, kieran-typescript, julik-frontend-races, adversarial.

Testing gaps:

  • No coverage for useLocalStorage('dashboard-toc-visible') round-trip across mount (the PR's headline persistence claim).
  • No coverage of inbound URL state where the same container id appears in both collapsed= and expanded= (precedence rule lives in isContainerCollapsed).
  • No assertion that DashboardContainer actually renders the DOM id that TOC_CONTAINER_ANCHOR_ID produces -- the producer-consumer link is asserted nowhere.

The "Show table of contents", "Collapse all sections", and "Expand all
sections" items are noise when a dashboard has no groups. Wrap the whole
View block in a `containers.length > 0` guard so they only appear once
the user has created at least one section. The collapse/expand items
remain visible-but-disabled when sections exist but none are collapsible,
matching the previous behaviour.
react-grid-layout's WidthProvider only listens to window resize events,
so toggling the right-rail TOC shrank the grid's flex column without
triggering a tile re-layout — tiles kept rendering at the old width and
the TOC overlapped them. Dispatch a synthetic resize event after the
layout commits to nudge RGL into remeasuring.
@kodiakhq kodiakhq Bot merged commit 1648c22 into main May 28, 2026
28 of 31 checks passed
@kodiakhq kodiakhq Bot deleted the dashboard-toc-right-rail branch May 28, 2026 15:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automerge review/tier-3 Standard — full human review required

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants