feat(dashboard): migrate to PageLayout with sticky query toolbar#2364
Conversation
Replaces the custom Dashboard page chrome with the shared `PageLayout` / `PageHeader` shell and introduces a reusable `stickyRow` slot on `PageHeader` so any page can pin a single row while the rest of the header scrolls away. On the dashboard this is used to keep the query toolbar (SQL/Lucene WHERE, time range, granularity, Live, refresh, edit filters, Run) visible while the user scrolls through tiles. The breadcrumbs, the editable dashboard name, dashboard actions (Favorite, Tags, Menu) and the "Created by … Updated …" meta all live in the scrolling chrome above; the meta moves to the right edge of the breadcrumbs row instead of taking its own body line. PageHeader changes: - New `stickyRow?: ReactNode` slot. When provided, the chrome `<header>` becomes non-sticky (and drops its bottom border / bottom padding so it reads as one block with the toolbar), and a sibling `<div>` renders the `stickyRow` at `position: sticky; top: 0`. Returning a Fragment of the two siblings means the sticky row's containing block is the page layout root, so it stays pinned for the full scroll length rather than being bounded by the chrome height. - `IntersectionObserver` tracks when the sticky row touches the scroll-container top and toggles a `.stickyRowAttached` class so the row has no `padding-top` while attached to chrome (tight spacing at rest) and the standard `--mantine-spacing-sm` top padding once stuck (breathing room from the viewport edge). The state is also exposed as `data-stuck` on the row for tests. - Pages without a `stickyRow` keep the existing fully-sticky header behavior — no migration needed elsewhere. Also drops a redundant `mt="sm"` from `DashboardFilters`, since the `PageLayout` `padded` content already provides top padding above it. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 00ddc83 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
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 |
🟡 Tier 3 — StandardIntroduces new logic, modifies core functionality, or touches areas with non-trivial risk. Why this tier:
Review process: Full human review — logic, architecture, edge cases. Stats
|
E2E Test Results✅ All tests passed • 191 passed • 3 skipped • 1323s
Tests ran across 4 shards in parallel. |
Deep Review✅ No critical issues found. 🟡 P2 -- recommended
🔵 P3 nitpicks (9)
Reviewers (8): correctness, testing, maintainability, project-standards, kieran-typescript, julik-frontend-races, adversarial, api-contract. Testing gaps:
|
## Summary
Migrates the Dashboard page to the shared `PageLayout` / `PageHeader` shell and introduces a reusable `stickyRow` slot on `PageHeader` so any page can pin a single row while the rest of the header scrolls away.
On the dashboard:
- **Sticky row**: the query toolbar (SQL/Lucene WHERE, time range, granularity, Live, refresh, edit filters, Run) stays pinned at the top of the scroll container while the user scrolls through tiles.
- **Scrolling chrome**: breadcrumbs, the editable dashboard name, dashboard actions (Favorite / Tags / Menu) and the "Created by … Updated …" meta sit above and scroll away with the page.
- **Meta moves into the breadcrumbs row** (right side) instead of taking its own body line.
- **Redundant `mt="sm"`** removed from `DashboardFilters` — `PageLayout`'s `padded` content already provides the top offset.
## The `stickyRow` slot
```tsx
<PageHeader
breadcrumbs={pageBreadcrumbs} // scrolls away
stickyRow={queryToolbar} // pinned to top — the only sticky row
>
{titleRow} // scrolls away
</PageHeader>
```
Behavior:
- When `stickyRow` is provided, the chrome `<header>` becomes non-sticky and drops its bottom border / bottom padding so it reads as one block with the toolbar at rest.
- `PageHeader` returns a Fragment of `<header>` + sticky `<div>` so the sticky row's containing block is the page layout root — it stays pinned for the full scroll length rather than being bounded by the chrome's height (the classic `position: sticky` gotcha).
- An `IntersectionObserver` tracks when the row touches the scroll-container top and toggles a `.stickyRowAttached` class so the row has no `padding-top` while attached to chrome (tight at rest) and standard padding once stuck (breathing room from the viewport edge). Also exposes `data-stuck` on the row for tests / debugging.
- When `stickyRow` is omitted, `PageHeader` behaves exactly like before — single sticky block. **No other page needs to change.**
## Test plan
- [ ] `yarn ci:unit` in `packages/app` still passes.
- [ ] Open a saved dashboard. Confirm:
- At rest: chrome (breadcrumbs + name + actions) and toolbar read as one continuous block, no gap and no extra horizontal line between them.
- Scrolling down: chrome scrolls away; the query toolbar stays pinned at the top of the scroll area with comfortable top padding.
- Scrolling back up: chrome reappears, toolbar tucks back into "attached" mode (top padding removed).
- "Created by … Updated …" meta sits on the right side of the breadcrumbs row with the existing tooltip preserved.
- [ ] Open any non-dashboard page that uses `PageHeader` (Alerts list, Search page, Sessions, Service Map, Kubernetes). Confirm the header still behaves identically to `main` (fully sticky, no visual regression).
- [ ] Open a dashboard with no defined dashboard variables — `DashboardFilters` row still sits at the right vertical offset (no doubled top padding).
Made with [Cursor](https://cursor.com)
Migrates the Dashboard page to the shared `PageLayout` / `PageHeader` shell and introduces a reusable `stickyRow` slot on `PageHeader` so any page can pin a single row while the rest of the header scrolls away.
On the dashboard:
- **Sticky row**: the query toolbar (SQL/Lucene WHERE, time range, granularity, Live, refresh, edit filters, Run) stays pinned at the top of the scroll container while the user scrolls through tiles.
- **Scrolling chrome**: breadcrumbs, the editable dashboard name, dashboard actions (Favorite / Tags / Menu) and the "Created by … Updated …" meta sit above and scroll away with the page.
- **Meta moves into the breadcrumbs row** (right side) instead of taking its own body line.
- **Redundant `mt="sm"`** removed from `DashboardFilters` — `PageLayout`'s `padded` content already provides the top offset.
```tsx
<PageHeader
breadcrumbs={pageBreadcrumbs} // scrolls away
stickyRow={queryToolbar} // pinned to top — the only sticky row
>
{titleRow} // scrolls away
</PageHeader>
```
Behavior:
- When `stickyRow` is provided, the chrome `<header>` becomes non-sticky and drops its bottom border / bottom padding so it reads as one block with the toolbar at rest.
- `PageHeader` returns a Fragment of `<header>` + sticky `<div>` so the sticky row's containing block is the page layout root — it stays pinned for the full scroll length rather than being bounded by the chrome's height (the classic `position: sticky` gotcha).
- An `IntersectionObserver` tracks when the row touches the scroll-container top and toggles a `.stickyRowAttached` class so the row has no `padding-top` while attached to chrome (tight at rest) and standard padding once stuck (breathing room from the viewport edge). Also exposes `data-stuck` on the row for tests / debugging.
- When `stickyRow` is omitted, `PageHeader` behaves exactly like before — single sticky block. **No other page needs to change.**
- [ ] `yarn ci:unit` in `packages/app` still passes.
- [ ] Open a saved dashboard. Confirm:
- At rest: chrome (breadcrumbs + name + actions) and toolbar read as one continuous block, no gap and no extra horizontal line between them.
- Scrolling down: chrome scrolls away; the query toolbar stays pinned at the top of the scroll area with comfortable top padding.
- Scrolling back up: chrome reappears, toolbar tucks back into "attached" mode (top padding removed).
- "Created by … Updated …" meta sits on the right side of the breadcrumbs row with the existing tooltip preserved.
- [ ] Open any non-dashboard page that uses `PageHeader` (Alerts list, Search page, Sessions, Service Map, Kubernetes). Confirm the header still behaves identically to `main` (fully sticky, no visual regression).
- [ ] Open a dashboard with no defined dashboard variables — `DashboardFilters` row still sits at the right vertical offset (no doubled top padding).
Made with [Cursor](https://cursor.com)
Summary
Migrates the Dashboard page to the shared
PageLayout/PageHeadershell and introduces a reusablestickyRowslot onPageHeaderso any page can pin a single row while the rest of the header scrolls away.On the dashboard:
mt="sm"removed fromDashboardFilters—PageLayout'spaddedcontent already provides the top offset.The
stickyRowslotBehavior:
stickyRowis provided, the chrome<header>becomes non-sticky and drops its bottom border / bottom padding so it reads as one block with the toolbar at rest.PageHeaderreturns a Fragment of<header>+ sticky<div>so the sticky row's containing block is the page layout root — it stays pinned for the full scroll length rather than being bounded by the chrome's height (the classicposition: stickygotcha).IntersectionObservertracks when the row touches the scroll-container top and toggles a.stickyRowAttachedclass so the row has nopadding-topwhile attached to chrome (tight at rest) and standard padding once stuck (breathing room from the viewport edge). Also exposesdata-stuckon the row for tests / debugging.stickyRowis omitted,PageHeaderbehaves exactly like before — single sticky block. No other page needs to change.Test plan
yarn ci:unitinpackages/appstill passes.PageHeader(Alerts list, Search page, Sessions, Service Map, Kubernetes). Confirm the header still behaves identically tomain(fully sticky, no visual regression).DashboardFiltersrow still sits at the right vertical offset (no doubled top padding).Made with Cursor