Skip to content

feat(dashboards): move tile editor to a drawer with docked settings panel - #2721

Closed
elizabetdev wants to merge 24 commits into
mainfrom
elizabet/tile-editor-drawer
Closed

feat(dashboards): move tile editor to a drawer with docked settings panel#2721
elizabetdev wants to merge 24 commits into
mainfrom
elizabet/tile-editor-drawer

Conversation

@elizabetdev

@elizabetdev elizabetdev commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Summary

Moves dashboard tile creation/editing from a centered modal into a right-side drawer, and reworks Display Settings so it no longer stacks a second drawer on top of the editor.

Closes HDX-4897

Why

Previously the tile editor was a Modal and Display Settings opened as a separate Drawer on top of it. With two stacked overlays, a single Esc press closed both at once, and the nested drawer-on-drawer felt wrong. This makes the editor a single drawer with settings docked inside it.

What changed

  • Modal → Drawer. EditTileModal is now EditTileDrawer (right-side, 90%, full-height flex body). Unsaved-changes confirm, z-index/ZIndexContext, and IsolatedChartSyncProvider behavior are preserved.
  • Display Settings as a docked side panel. Instead of a nested drawer, Display Settings render as a full-height panel beside the editor/preview, so the tile stays visible while you change its options (e.g. applying a color on a Number tile). A single Esc now closes just the panel, not the whole editor.
  • Heatmap settings too. The heatmap "Display Settings" (HeatmapSettingsDrawer) uses the same docked panel via a new shared SettingsSidePanel shell. Its use on the Search page (DBSearchHeatmapChart) is unchanged (still a Drawer, asPanel is opt-in).
  • Full-width tab bar. The display-type tabs sit in a full-width bar above the editor+panel row, so they never wrap when the panel is open. The panel's border-left meets the tab underline cleanly (semantic --color-border token).
  • No stale panels. Switching to a tab without settings (Search / Patterns / Markdown) or to the other variant (heatmap ↔ regular) closes the non-applicable panel.
  • Chart Explorer is unaffected — all the drawer/panel/full-height behavior is gated behind isDashboardForm; there the editor still uses the overlay Drawer.

Screenshots

Before

My-Dashboard-–-ClickStack-07-23-2026_07_28_PM

After

My-Dashboard-–-ClickStack-07-23-2026_07_24_PM

Testing

  • make ci-lint equivalent: yarn lint:fix + yarn tsc --noEmit clean.
  • Unit: ChartDisplaySettingsDrawer, DBSearchHeatmapChart, DBEditTimeChartForm suites pass.

Changeset

.changeset/tile-editor-drawer.md@hyperdx/app patch.

Made with Cursor

…anel

Convert the dashboard tile create/edit modal into a right-side drawer.
Display Settings (and the heatmap variant) now dock as a full-height side
panel beside the editor/preview instead of stacking a second drawer, so the
tile stays visible while its options change and a single Esc closes only the
panel. The display-type tabs sit in a full-width bar above the editor+panel
row so they never wrap, and switching to a tab without settings (Search /
Patterns / Markdown) or the other variant closes the non-applicable panel.

Co-authored-by: Cursor <cursoragent@cursor.com>
@changeset-bot

changeset-bot Bot commented Jul 23, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: dbf20de

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 Minor
@hyperdx/api Minor
@hyperdx/otel-collector Minor

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

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
hyperdx-oss Ready Ready Preview Aug 4, 2026 11:09am
hyperdx-storybook Ready Ready Preview Aug 4, 2026 11:09am

Request Review

@github-actions github-actions Bot added the review/tier-3 Standard — full human review required label Jul 23, 2026
@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🔴 Tier 4 — Critical

Touches auth, data models, config, tasks, OTel pipeline, ClickHouse, or CI/CD.

Why this tier:

  • Large diff: 1179 production lines changed (threshold: 1000)

Review process: Deep review from a domain expert. Synchronous walkthrough may be required.
SLA: Schedule synchronous review within 2 business days.

Stats
  • Production files changed: 8
  • Production lines changed: 1179 (+ 409 in test files, excluded from tier calculation)
  • Branch: elizabet/tile-editor-drawer
  • Author: elizabetdev

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

@greptile-apps

greptile-apps Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR moves dashboard tile editing into a right-side drawer and consolidates display, row-click, and per-series settings into an integrated settings rail.

  • Disables the containing drawer's Escape dismissal while the docked settings rail is open.
  • Routes Escape to the currently active nested overlay or settings rail.
  • Writes settings directly to the tile draft, leaving the tile-level Save and Cancel actions as the commit boundary.
  • Adds compact series controls, updated dashboard interaction hooks, and regression coverage for drawer and Escape behavior.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains in the previously reported Escape-handling paths.

Important Files Changed

Filename Overview
packages/app/src/DBDashboardPage.tsx Replaces the tile modal with a drawer and disables drawer-level Escape dismissal while a docked settings panel is active.
packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx Coordinates the settings rail, drawer state, and nested-overlay Escape ownership; the previously reported Escape paths are addressed.
packages/app/src/components/ChartDisplaySettingsDrawer.tsx Converts display settings from a separately applied drawer into a live-updating section embedded in the tile settings rail.
packages/app/src/components/DBEditTimeChartForm/TileSettingsRail.tsx Introduces the unified docked shell for display and row-click settings.
packages/app/src/components/DBEditTimeChartForm/ChartSeriesEditor.tsx Reworks series editing into compact expandable cards with popover-based formatting and color controls.
packages/app/src/components/DBEditTimeChartForm/tests/DBEditTimeChartForm.test.tsx Adds regression coverage for panel ownership, nested overlays, and sequential panel-then-drawer Escape dismissal.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Dashboard[Dashboard] --> Drawer[Tile editor drawer]
  Drawer --> Editor[Chart editor and preview]
  Drawer --> Rail[Docked tile settings rail]
  Rail --> Display[Display settings]
  Rail --> RowClick[Row-click action]
  Editor --> Popover[Per-series popovers]
  Popover -->|First Escape| Editor
  Rail -->|First Escape| Drawer
  Drawer -->|Next Escape| Dashboard
Loading

Reviews (19): Last reviewed commit: "feat(dashboards): restructure tile edito..." | Re-trigger Greptile

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

E2E Test Results

1 test failed • 264 passed • 1 skipped • 1098s

Status Count
✅ Passed 264
❌ Failed 1
⚠️ Flaky 1
⏭️ Skipped 1

Tests ran across 4 shards in parallel.

View full report →

@github-actions

github-actions Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

<!-- deep-review -->

Deep Review

🔴 P0/P1 -- must fix

  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:374 -- The Escape bail-out selector includes [role="dialog"], which Element.closest() matches on the enclosing Mantine Drawer content that wraps the whole editor, so the capture handler returns before e.stopPropagation() on every keypress and the entire tile editor closes instead of just the settings panel.
    • Fix: Bail only when the matched [role="dialog"] is not the editor's own Drawer content (compare against a ref/parentRef.closest(...)), and drive the drawer's Escape explicitly via closeOnEscape={false} plus an owned handler, since stopPropagation() cannot defeat Mantine's own window-capture Escape listener documented at packages/app/tests/e2e/utils/locators.ts:13-19.
    • correctness, adversarial, julik-frontend-races

🟡 P2 -- recommended

  • packages/app/src/components/ChartDisplaySettingsDrawer.tsx:148 -- The reset effect re-runs reset(appliedDefaults) on every appliedDefaults identity change while opened is true, and getTraceDurationNumberFormat (packages/app/src/source.ts:517-522) returns a fresh object literal per call, so editing a series in the now-interactive editor column silently discards un-applied panel edits.

    • Fix: Gate the reset on the closed→open transition using a previous-opened ref, and memoize autoDetectedNumberFormat on its primitive inputs so it stops changing identity per keystroke.
    • correctness, kieran-typescript, adversarial
  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:400 -- The tab-change cleanup only closes the panel when the new display type has no settings view, so a Line→Table switch leaves un-applied Line-only values (alignDateRangeToGranularity, seriesLimit) in the retained form, and applyChanges writes the whole form on the next Apply.

    • Fix: Close or force a reset of the settings form on every display-type change while the panel is open, not only when the new type lacks a settings view.
    • adversarial
  • packages/app/src/components/SettingsSidePanel.tsx:30 -- The panel is a bare Box with no role, no accessible name, and no focus return, dropping the role="dialog" + "Display Settings" name that the Drawer it replaces exposed and that ChartEditorComponent.ts:540 still relies on for the Chart Explorer variant.

    • Fix: Add role="region" with aria-label={title} (or aria-labelledby wired to the header), and restore focus to the triggering control when the panel unmounts.
    • agent-native, adversarial
  • packages/app/tests/e2e/components/ChartEditorComponent.ts:537 -- displaySettingsContainer matches only display-settings-panel or role=dialog[name="Display Settings"], but the docked heatmap variant renders data-testid="heatmap-settings-panel" with no dialog role, so the shared helpers cannot reach heatmap settings in dashboard mode and applyDisplaySettings's waitFor({ state: 'hidden' }) passes vacuously against zero matches.

    • Fix: Add .or(this.page.getByTestId('heatmap-settings-panel')) to the union, or expose a separate heatmapSettingsContainer getter and select the variant explicitly.
    • testing, adversarial
  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:366 -- The capture-phase Escape handler that is the PR's central behavior has no unit or e2e coverage: DBEditTimeChartForm.test.tsx never passes isDashboardForm, and the two dashboard.spec.ts Escape tests press Escape only when no panel is open.

    • Fix: Add an e2e case that opens the tile editor and Display Settings, presses Escape once, and asserts the panel is hidden while chart-name-input stays visible.
    • testing, correctness, adversarial, julik-frontend-races
🔵 P3 nitpicks (4)
  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:377 -- For genuinely nested widgets the guard returns without calling stopPropagation(), so an Escape dismissing a CodeMirror autocomplete inside the docked heatmap panel still reaches the Drawer and tears down the editor.

    • Fix: Call e.stopPropagation() for nested-widget targets too while skipping the panel-close calls, so the surrounding Drawer never sees the key.
  • packages/app/src/components/ChartDisplaySettingsDrawer.tsx:428 -- The asPanel shell fork, the reset-on-open effect, and handleClose are duplicated near-verbatim in HeatmapSettingsDrawer.tsx:147, including an identical comment block, so any fix must land twice.

    • Fix: Extract a shared SettingsShell wrapper plus a useResetOnOpen hook and have both components call them.
  • packages/app/src/components/SettingsSidePanel.tsx:32 -- The container uses a raw style object with an unnamed width: 340, against the guidance in agent_docs/code_style.md:34 to prefer individual Mantine style props.

    • Fix: Use Mantine props (w, h, display) and hoist the width into a named constant, keeping only the var(--color-border) borders as inline style.
  • packages/app/tests/e2e/components/ChartEditorComponent.ts:630 -- getPreviewTableHeaders and getPreviewTableCellTexts scope to the third-party .mantine-Drawer-body class, which does not exist in Chart Explorer mode and can also match a concurrently open Row Click or Series Display Settings drawer.

    • Fix: Add a first-party data-testid to the editor's scroll container and scope both helpers to it.

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

Testing gaps:

  • No coverage for Escape originating inside the docked panel's CodeMirror editors or a Mantine Select/Popover while the panel is open — the exact targets the guard whitelists.
  • No coverage for editing the chart builder while the settings panel is docked open, the interaction the docked layout newly enables.
  • No test switches display types with the panel open to assert only currently visible options are persisted on Apply.
  • The asPanel branch of HeatmapSettingsDrawer is unexercised: DBSearchHeatmapChart.test.tsx:51 mocks the component wholesale, and no dashboard e2e covers Heatmap tiles.
  • No test asserts where focus lands after the panel unmounts, or that the editor still responds to Escape afterwards.

Note: bash/git were unavailable in this environment, so reviewers worked from the working-tree files rather than a computed diff; new-vs-pre-existing attribution was inferred from code structure and may be imprecise for the three large pre-existing files.

The dashboard tile editor was converted from a Modal to a Drawer, with
Display Settings now rendered as a docked side panel instead of a nested
Drawer. Update the shared ChartEditorComponent page object to match:

- Preview table helpers now target .mantine-Drawer-body instead of
  .mantine-Modal-body
- Display Settings helpers match either the dashboard side panel
  (data-testid) or the Chart Explorer drawer (role=dialog) so both
  contexts work

Co-authored-by: Cursor <cursoragent@cursor.com>
setGroupByColumnsOnLeft still targeted the Display Settings dialog
directly, which times out in the dashboard where Display Settings is now
a docked side panel. Route it through the shared displaySettingsContainer
locator so it matches either the panel or the drawer.

Co-authored-by: Cursor <cursoragent@cursor.com>
…p swallowing nested Esc

The tile editor's Esc interceptor and tab-change cleanup close the
settings panels via the bare useDisclosure close(), bypassing each
child's handleClose reset. Abandoned sub-form edits therefore stayed
latched and were written into the tile config by the next Apply. Reset
the sub-form on the closed→open transition in both ChartDisplaySettingsDrawer
and HeatmapSettingsDrawer so every dismiss path behaves like cancel.

Also stop the capture-phase Esc handler from calling stopPropagation on
every Escape: bail out when the key originated inside a nested overlay
(popover dropdown, code editor, select listbox, dialog) or was already
handled, so those widgets can consume their own Esc instead of having it
swallowed before they ever see it.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions github-actions Bot added review/tier-4 Critical — deep review + domain expert sign-off and removed review/tier-3 Standard — full human review required labels Jul 27, 2026
Comment thread packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx Outdated
The tile editor's own Drawer has role="dialog", so target.closest() matched
it and the handler bailed before stopping propagation, closing the whole
editor on Esc. Ignore a match that wraps the form (the containing drawer) and
only bail for overlays genuinely nested inside the panel.

Co-authored-by: Cursor <cursoragent@cursor.com>
Comment thread packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx Outdated
The chart-wide/per-series number format E2E helper still used the native
selectOption API against getByLabel('Output format'), which now resolves to
both the Mantine Select input and its listbox (strict-mode violation). Open the
combobox and click the option, matching the pattern used by the other Select
helpers in this component.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

github-actions Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

<!-- deep-review -->

Deep Review

⚠️ Degraded run — the multi-agent pipeline did not execute. Bash fails at sandbox init for every command (bwrap: Can't create file at /home/.mcp.json: Permission denied), including with the sandbox disabled, and no Grep/Glob tools exist in this session. WebFetch was denied by permissions, so the diff could not be retrieved over HTTP either. Consequently no git diff was available: findings below come from directly reading the current checkout of the files named in the PR, not from the diff. Nothing here is diff-attributed, so a finding could in principle describe pre-existing code, and changed files I could not locate by path guessing are not covered. Treat this as a partial manual review, not the requested 6–13 persona fan-out.

✅ No critical issues found.

🟡 P2 — recommended

  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:411 — The window-level Escape handler that owns the docked panel exempts popovers, listboxes and aria-expanded="true" comboboxes but not dialogs, so an overlay opened after the panel has its Escape consumed and the panel underneath closes instead of the topmost layer.
    • Fix: Add [role="dialog"] to the closest() exemption selector, or bail out whenever an element with [role="dialog"] is present above the panel in the DOM.
  • packages/app/src/components/HeatmapSettingsDrawer.tsx:61 — The reset effect is level-triggered on defaultValues, so any parent change to the watched series.0.* values re-runs form.reset while the panel is open and silently discards unapplied edits; changing the source on the Heatmap tab reaches applyHeatmapDefaults at EditTimeChartForm.tsx:683 and does exactly this, which the docked panel newly makes possible because the source picker stays reachable.
    • Fix: Make the reset edge-triggered on the closed→open transition with a wasOpenedRef, matching the pattern already used at ChartDisplaySettingsDrawer.tsx:151.
  • packages/app/src/components/SettingsSidePanel.tsx:30 — The panel replaces a Mantine Drawer with a plain Box that carries no role, no accessible name, and no focus management, so the settings region is neither announced to assistive technology nor focused on open, and focus is not restored on close.
    • Fix: Give the wrapper role="dialog" with an aria-label tied to title, move focus into the panel on open, and return focus to the invoking control on close.
  • packages/app/src/components/HeatmapSettingsDrawer.tsx:147 — The new asPanel branch and the new shared SettingsSidePanel shell have no test files, leaving the heatmap variant of the docked-panel behavior uncovered while the regular variant does have ChartDisplaySettingsDrawer.test.tsx and an Esc-contract suite in DBEditTimeChartForm.test.tsx.
    • Fix: Add a test asserting asPanel renders the panel rather than a Drawer and that in-panel edits survive unrelated parent form updates.
🔵 P3 nitpicks (1)
  • packages/app/src/components/SettingsSidePanel.tsx:36width: min(340px, 45%) combined with the editor column's minWidth: 320 at EditTimeChartForm.tsx:923 sums past the row width on narrow viewports, so the flex row overflows instead of the panel yielding as the comment intends.
    • Fix: Let the editor column shrink below its floor at small widths via a media query, or drop the fixed floor in favour of a flex-basis that shrinks.

Reviewers (0): none — the persona sub-agent fan-out could not run. Findings are from a direct read of EditTimeChartForm.tsx, ChartDisplaySettingsDrawer.tsx, HeatmapSettingsDrawer.tsx, SettingsSidePanel.tsx, and the inline EditTileDrawer in DBDashboardPage.tsx, plus one file-locator agent.

Testing gaps:

  • No test file for SettingsSidePanel or for the asPanel branch of HeatmapSettingsDrawer.
  • No test covers Escape precedence when a dialog is layered over a docked settings panel.
  • Coverage is unverifiable beyond this: without a diff, changed files not reachable by path guessing were not reviewed at all.

The docked settings panel reported its open state to the containing tile
Drawer through a passive effect, so closeOnEscape flipped to false only
after paint. An Esc pressed right after opening a panel still reached
Mantine's capture-phase handler while closeOnEscape was true and tore down
the entire editor. Report the panel state from a layout effect (pre-paint)
and attach the panel's own Esc listener the same way, preventDefault after
closing so any late listener bails on the defaultPrevented guard, and only
close the panel that is actually open.

Add regression coverage for behavior that had none: the docked-panel Esc
contract in DBEditTimeChartForm (isDashboardForm) and the asPanel render
path in ChartDisplaySettingsDrawer.

Co-authored-by: Cursor <cursoragent@cursor.com>
elizabetdev and others added 2 commits July 28, 2026 15:22
- Narrow the settings-panel Esc exemption from a bare [aria-expanded="true"]
  to combobox-like controls only, so a focused Accordion.Control in the
  preview no longer swallows Esc and freezes the panel open.
- Reset the parent drawer's settingsPanelOpen flag when the form unmounts
  with a panel still docked, so its Esc-to-close is not left disabled.
- Make HeatmapSettingsDrawer's reset edge-triggered on the closed->open
  transition (wasOpenedRef), matching ChartDisplaySettingsDrawer, so a
  source/field change while the panel is open no longer wipes unapplied edits.
- Give SettingsSidePanel an accessible landmark: role="region" labelled by a
  heading title, restoring the accessible name the replaced Drawer provided.

Add regression tests for the narrowed Esc exemption (combobox stays open,
non-combobox disclosure closes) and the panel's labelled-region role.

Co-authored-by: Cursor <cursoragent@cursor.com>
…ction

The dashboard's tile-selection Escape hotkey (useTileSelection) fired with
Mantine's default preventDefault, marking the keypress handled on
documentElement before it reached the docked settings panel's window listener,
which bails on event.defaultPrevented. With the tile-editor Drawer's
closeOnEscape disabled while a panel is open, Esc then did nothing. Make the
selection-clear hotkey passive (preventDefault:false) so the panel keeps
owning Esc.

Add regression coverage (useTileSelection Esc clears selection while leaving
defaultPrevented false; the panel closes on Esc inside a real Drawer) and give
the per-series number-format disclaimer an icon on the shared warning Alert.

Co-authored-by: Cursor <cursoragent@cursor.com>
…drawer

Co-authored-by: Cursor <cursoragent@cursor.com>

# Conflicts:
#	packages/app/src/DBDashboardPage.tsx
@github-actions

Copy link
Copy Markdown
Contributor

<!-- deep-review -->

Deep Review

Degraded run — please read. This checkout could not produce a diff: the Bash tool fails on every invocation (bwrap: Can't create file at /home/.mcp.json: Permission denied, including with the sandbox disabled), Grep/Glob are not registered in this session, and WebFetch/WebSearch are not permitted. Findings below come from reading the current head state of the touched files via Read only, so introduced-vs-pre-existing could not be confirmed against 8aeb2f3. Six reviewer agents were dispatched; two returned before this report was due (see footer).

✅ No critical issues found. No P0/P1: the Esc-ownership handshake, the closeOnEscape wiring, the panel state machine, and the tab-change panel-closing effect all check out on inspection, and the Esc contract has real unit coverage including a Drawer-wrapped harness (packages/app/src/components/DBEditTimeChartForm/__tests__/DBEditTimeChartForm.test.tsx:742).

🟡 P2 -- recommended

  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:412 -- The panel's window keydown listener swallows Esc for any overlay stacked above the docked panel, because its exemption selector only covers .mantine-Popover-dropdown, [role="listbox"], and expanded combobox roles — a Mantine Modal matches none of them, so Esc pressed to dismiss the unsaved-changes confirm dialog raised by handleClose instead calls e.preventDefault() and closes Display Settings behind the dialog.
    • Fix: Bail out of the handler when a higher overlay is present — e.g. also exempt [role="dialog"]/.mantine-Modal-content ancestors, or scope the listener to the drawer subtree rather than window.
  • packages/app/src/components/HeatmapSettingsDrawer.tsx:154 -- The heatmap asPanel branch has no test coverage at all: there is no HeatmapSettingsDrawer test file, and no test in DBEditTimeChartForm.test.tsx renders DisplayType.Heatmap, so the heatmap-settings-panel testid, the role="region" landmark, and the close-button wiring are unverified even though ChartDisplaySettingsDrawer got a dedicated asPanel suite.
    • Fix: Add a HeatmapSettingsDrawer test mirroring the asPanel (docked side panel) mode block at packages/app/src/components/__tests__/ChartDisplaySettingsDrawer.test.tsx:575.
    • testing
  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:453 -- The new effect that closes a non-applicable panel on display-type change has two independent conditional closes and neither is exercised by any test, so a stale or wrong-variant panel lingering after a tab switch would ship unnoticed.
    • Fix: Add isDashboardForm tests covering Number→Search, Line→Heatmap, and Heatmap→Line with a panel open.
    • testing
🔵 P3 nitpicks (7)
  • packages/app/src/DBDashboardPage.tsx:1487 -- With the drawer body at overflow: 'hidden', the editor column's minWidth: 320 plus the panel's flexShrink: 0; width: min(340px, 45%) cannot both fit below roughly 580px of drawer width, so on a very narrow viewport the panel's close and Apply controls are clipped with no scrollbar to reach them.
    • Fix: Allow the row to scroll horizontally, or drop the editor minWidth floor / panel flexShrink: 0 below a breakpoint.
  • packages/app/src/components/ChartDisplaySettingsDrawer.tsx:118 -- ChartDisplaySettingsDrawer, HeatmapSettingsDrawer, and the DrawerFormValues type keep "Drawer" in their names although the dashboard path — now the primary caller via asPanel={isDashboardForm} — renders a plain docked Box/ScrollArea with no dialog role or focus trap.
    • Fix: Rename the components and the internal form-values type to mode-neutral names such as ChartDisplaySettingsPanel / SettingsFormValues.
    • maintainability
  • packages/app/src/components/HeatmapSettingsDrawer.tsx:66 -- The wasOpenedRef edge-triggered reset is duplicated verbatim in both settings components, with the requirement that they stay identical enforced only by a prose comment.
    • Fix: Extract a shared useEdgeTriggeredReset(opened, reset, values) hook and use it from both components.
    • maintainability
  • packages/app/src/components/HeatmapSettingsDrawer.tsx:152 -- The if (asPanel) { … } return <Drawer …> wrapper branch is duplicated across both settings components, letting the two Drawer configurations drift independently (one sets size="sm" and lockScroll={false}, the other does not).
    • Fix: Extract a shared SettingsContainer that picks between SettingsSidePanel and Drawer, making the intentional config differences explicit.
    • maintainability
  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:828 -- isDashboardForm now means four unrelated things at once — drawer-vs-page layout, full-height flex, the editor minWidth floor, and the asPanel switch passed down to two children — so a future caller needing one behavior without the others has to bend the prop further.
    • Fix: Derive separately named values from isDashboardForm (or split "hosted in a drawer" from "children render as docked panels") so the concerns can vary independently.
    • maintainability
  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:917 -- The same isDashboardForm condition is re-tested in roughly six inline style/prop ternaries with different literal payloads, so a later layout tweak is easy to apply to some sites and miss others.
    • Fix: Hoist the dashboard-mode layout values into one object computed near the top of the render.
    • maintainability
  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:430 -- The heatmapSettingsOpened arm of the Esc handler and the unmount-cleanup reset of onSettingsPanelOpenChange are both untested; existing Esc tests only ever open regular Display Settings and always dismiss via a keypress rather than unmounting.
    • Fix: Add an Esc test on the Heatmap tab and one that unmounts the form with a panel still docked, asserting the parent flag is reset to false.
    • testing

Reviewers (3): orchestrator direct file analysis, testing, maintainability. Four dispatched reviewers — correctness, adversarial, kieran-typescript, julik-frontend-races — had not returned when this report was due, so their coverage is missing; the P0/P1-clean verdict rests only on the three above.

Testing gaps:

  • Heatmap asPanel panel rendering, close wiring, and Esc behavior are entirely uncovered.
  • The tab-change panel-closing effect has no coverage for any of its three transitions.
  • SettingsSidePanel has no dedicated test; its contract is verified only indirectly through the ChartDisplaySettingsDrawer asPanel suite.
  • No test exercises Esc while a third overlay (the discard-confirm Modal) sits above a docked panel — the scenario behind the P2 above.
  • The real EditTileDrawer in DBDashboardPage.tsx is not integration-tested; the Esc contract is verified against a hand-rolled Drawer harness that mimics rather than imports it.

… under lint warning cap

The main merge pushed the app's eslint warning total to 741, one over the
--max-warnings 740 cap, failing CI lint. Construct a fully-typed Dashboard in
the Escape hotkey test instead of casting through `unknown`, removing the
warning and returning to 740.

Co-authored-by: Cursor <cursoragent@cursor.com>
…wer Escape

The kiosk-mode Escape hotkey was registered as ['Escape', exitKioskMode],
which fired on every Escape (even when not in kiosk mode) and preventDefaulted
by default. Because Mantine's useHotkeys listens on documentElement and bubbles
before window-level Esc handlers, this marked every Escape as handled, so the
tile editor's docked settings panel — which bails on event.defaultPrevented —
could never close, and the drawer's closeOnEscape stayed disabled behind it.
Escape appeared completely dead while a settings panel was open.

Only consume Escape while actually in kiosk mode and never preventDefault, so
Esc falls through to the editor's panel (close panel) and then the drawer
(close drawer) as expected. Adds regression tests for the kiosk hotkey and the
two-step panel/drawer Escape contract.

Co-authored-by: Cursor <cursoragent@cursor.com>
@github-actions

Copy link
Copy Markdown
Contributor

Deep Review

⚠️ Degraded run — read this before trusting coverage. bash is non-functional in this environment (every invocation, sandboxed or not, fails at the sandbox layer with bwrap: Can't create file at /home/.mcp.json: Permission denied), and no Grep, Glob, or WebFetch tool is exposed. git diff against 8aeb2f3 was therefore not obtainable, and neither was the GitHub patch. Findings below come from reading the checked-out PR head directly with Read, so they are anchored to post-change line numbers but cannot distinguish added lines from pre-existing ones. The persona sub-agent fan-out was dispatched but returned no results before the deadline; what follows is the orchestrator's own verified analysis, not a 7-reviewer merge. Treat coverage as partial.

Scope reconstructed by reading: packages/app/src/DBDashboardPage.tsx (EditTileDrawer, lines 1421-1516), packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx, packages/app/src/components/SettingsSidePanel.tsx (new), packages/app/src/components/ChartDisplaySettingsDrawer.tsx, packages/app/src/components/HeatmapSettingsDrawer.tsx, packages/app/src/components/DBEditTimeChartForm/ChartEditorControls.tsx, .changeset/tile-editor-drawer.md.

✅ No critical issues found.

Two things I specifically tried to break and could not: (1) two panels docked at once — on the Heatmap tab ChartEditorControls.tsx:175 routes to openHeatmapSettings and the regular Display Settings button at ChartEditorControls.tsx:399 sits in a branch Heatmap never reaches, and the tab-change effect at EditTimeChartForm.tsx:453-463 closes the non-applicable panel, so the displaySettingsOpened && heatmapSettingsOpened state is not reachable through the UI; (2) the layout-effect / closeOnEscape handoff — the parent passes the stable setSettingsPanelOpen setState reference (DBDashboardPage.tsx:1507), so the cleanup-on-every-dep-change in EditTimeChartForm.tsx:382-389 does not thrash parent state or loop. The CodeMirror carve-out also holds: the inner editor handler runs before the window-level one, so the defaultPrevented guard at EditTimeChartForm.tsx:415 covers it.

🟡 P2 -- recommended

  • packages/app/src/components/SettingsSidePanel.tsx:35 -- The docked panel is a role="region" with no focus management, so activating Display Settings leaves keyboard focus on the trigger button and the panel is rendered after the entire editor column in DOM order (EditTimeChartForm.tsx:1075), forcing a keyboard user to tab through the whole editor and preview to reach the settings controls; the Mantine Drawer it replaced moved and trapped focus automatically.
    • Fix: Move focus to the panel heading or first control when opened transitions to true, and restore focus to the triggering button on close.
  • packages/app/src/components/SettingsSidePanel.tsx:43 -- The panel is flexShrink: 0 at width: min(340px, 45%) beside an editor column floored at minWidth: 320 (EditTimeChartForm.tsx:931) inside a drawer body set to overflow: hidden (DBDashboardPage.tsx:1487) at size="90%", so at a 600px viewport the row needs 320 + 243 = 563px against 540px available and roughly 23px of the panel is clipped with no scroll affordance to recover it.
    • Fix: Let the row scroll horizontally or drop the editor's minWidth floor below the panel-plus-editor budget at narrow widths instead of clipping under overflow: hidden.
🔵 P3 nitpicks (5)
  • packages/app/src/DBDashboardPage.tsx:1479 -- The tile editor Drawer sets withCloseButton={false} with no title, leaving the role="dialog" without an accessible name for screen-reader users.
    • Fix: Pass an aria-label (or a visually hidden title) naming the drawer, e.g. the tile editor's purpose.
  • packages/app/src/components/HeatmapSettingsDrawer.tsx:158 -- The heatmap variant hardcodes title="Display Settings", identical to the regular variant at ChartDisplaySettingsDrawer.tsx:442, so both docked panels expose the same accessible name and the same Close Display Settings button label despite being different surfaces.
    • Fix: Give the heatmap panel a distinct title such as Heatmap Display Settings.
  • packages/app/src/components/ChartDisplaySettingsDrawer.tsx:118 -- Both components are still named ...SettingsDrawer while their primary dashboard path now renders a docked panel rather than a drawer, which misleads the next reader about what the component produces.
    • Fix: Rename to a surface-neutral name such as ChartDisplaySettings and HeatmapSettings.
  • packages/app/src/components/ChartDisplaySettingsDrawer.tsx:152 -- The edge-triggered wasOpenedRef reset is duplicated in HeatmapSettingsDrawer.tsx:66-73 with a comment asking that the two be kept "in lockstep", which is a correctness invariant enforced only by prose across two files.
    • Fix: Extract the reset into a shared useEdgeTriggeredReset(opened, reset, values) hook used by both.
  • packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx:157 -- isDashboardForm now gates at least eight unrelated concerns (flex column, full height, tab-bar padding, editor padding, minWidth floor, overflowY, asPanel on two children, the window Escape listener, and the parent notification), so it has become an implicit "am I inside a drawer" mode flag that will keep accreting branches.
    • Fix: Rename it to reflect the real axis (e.g. isDockedInDrawer) or split the layout concerns into a wrapper component that owns the drawer-specific chrome.

Reviewers (0 of 7 returned): correctness, adversarial, julik-frontend-races, testing, maintainability, kieran-typescript, project-standards were all dispatched; none reported before the deadline, so all findings above are the orchestrator's own direct code reading. Re-run in an environment with a working shell for the full merge.

Testing gaps:

  • Could not verify whether any test file exists for the new SettingsSidePanel component, or whether the asPanel={true} branch of either settings component is covered — no search tooling was available to locate test files, so the author's claim that the ChartDisplaySettingsDrawer, DBSearchHeatmapChart, and DBEditTimeChartForm suites pass is unverified here.
  • The headline behavior of this PR — one Escape closes only the docked panel and never the tile editor — depends on the interaction between closeOnEscape={!settingsPanelOpen} (DBDashboardPage.tsx:1478) and the window listener at EditTimeChartForm.tsx:412-440; that contract, plus the cleanup path that resets settingsPanelOpen for the next tile, is worth an explicit integration test.
  • The Escape handler calls preventDefault() unconditionally once it acts (EditTimeChartForm.tsx:428); the interaction with the unsaved-changes confirm dialog opened from handleClose while a panel is still docked was not verifiable without running the app, and is worth a manual check.
  • --color-border (used at SettingsSidePanel.tsx:47 and :55) was not confirmed to be a defined token in this codebase; an undefined custom property renders no border at all.

@jordan-simonovski jordan-simonovski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd only address the one blocking issue here, but everything LGTM.

My only other concern is that the files are getting a bit unwieldy, but that's non-blocking.

// above: the panel must own Esc from the very first frame it is visible, so
// an Esc pressed immediately after opening closes the panel rather than
// doing nothing (or racing the drawer's own listener).
useIsomorphicEffect(() => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (blocking): a nested overlay with role="dialog" is no longer exempt, so one Esc dismisses both it and the docked panel.

Failure mode. Open a Line tile → Display Settings (panel docks) → change Output format to Bytes but don't Apply → click a series' number-format button, so Series Display Settings opens over the top → press Esc to back out of that drawer. The drawer closes (its own capture-phase listener) and the docked panel closes on the same keypress, discarding the unapplied Bytes edit. Same with Esc on the "discard changes?" confirm: backing out of the confirm silently also throws away the panel's edits.

const target = e.target;
if (
target instanceof HTMLElement &&
target.closest(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (non-blocking): Could we use Mantine's own open-combobox marker?

disclaimer={
isPerSeriesNumberFormatAllowed ? (
<Alert
variant="warning"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: nice cleanup swapping variant="outline" color="yellow" for the semantic variant="warning" + icon.

…nspector rail

Consolidate the tile editor's Display Settings, Row Click, per-series color,
and number-format drawers into a single docked settings rail plus row-anchored
popovers. All sub-edits now write live to the form draft with the tile's
Save/Cancel as the single commit point, and series rows are compact with an
expand toggle and kebab menu.

Co-authored-by: Cursor <cursoragent@cursor.com>
@elizabetdev
elizabetdev marked this pull request as draft August 3, 2026 15:04
…design

Rework the dashboard widget tile editor (EditTimeChartForm) to follow the
new Add Widget Editor layout:

- Move the widget title into a top header bar with a close button, and lift
  Save/Cancel into that header (hidden in the in-body action bar).
- Split the editor column into a sunken-gray query-builder region and a white
  preview region; give the viz-type tabs bar and the tile settings rail the
  same sunken-gray background (--color-bg-sunken).
- Render each series as a bordered card with a header row (name/alias/format/
  color/actions) instead of divider-separated blocks.
- Unify the query "Row 1": Data Source + Builder/SQL/PromQL mode toggle + Run,
  hiding the duplicate Run in the action bar for the builder view.
- Drop the dividers around the chart-level Group By and the Add Series footer.

Chart Explorer (non-dashboard) layout is unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

review/tier-4 Critical — deep review + domain expert sign-off

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants