Bumps [hono](https://github.com/honojs/hono) from 4.12.9 to 4.12.12.
e notes</summary>
<p><em>Sourced from <a href="https://github.com/honojs/hono/releases">hono's releases</a>.</em></p>
<blockquote>
<h2>v4.12.12</h2>
<h2>Security fixes</h2>
<p>This release includes fixes for the following security issues:</p>
<h3>Middleware bypass via repeated slashes in serveStatic</h3>
<p>Affects: Serve Static middleware. Fixes a path normalization inconsistency where repeated slashes (<code>//</code>) could bypass route-based middleware protections and allow access to protected static files. GHSA-wmmm-f939-6g9c</p>
<h3>Path traversal in toSSG() allows writing files outside the output directory</h3>
<p>Affects: <code>toSSG()</code> for Static Site Generation. Fixes a path traversal issue where crafted <code>ssgParams</code> values could write files outside the configured output directory. GHSA-xf4j-xp2r-rqqx</p>
<h3>Incorrect IP matching in ipRestriction() for IPv4-mapped IPv6 addresses</h3>
<p>Affects: IP Restriction Middleware. Fixes improper handling of IPv4-mapped IPv6 addresses (e.g. <code>::ffff:127.0.0.1</code>) that could cause allow/deny rules to be bypassed. GHSA-xpcf-pg52-r92g</p>
<h3>Missing validation of cookie name on write path in setCookie()</h3>
<p>Affects: <code>setCookie()</code>, <code>serialize()</code>, and <code>serializeSigned()</code> from <code>hono/cookie</code>. Fixes missing validation of cookie names on the write path, preventing inconsistent handling between parsing and serialization. GHSA-26pp-8wgv-hjvm</p>
<h3>Non-breaking space prefix bypass in cookie name handling in getCookie()</h3>
<p>Affects: <code>getCookie()</code> from <code>hono/cookie</code>. Fixes a discrepancy in cookie name handling that could allow attacker-controlled cookies to override legitimate ones and bypass prefix protections. GHSA-r5rp-j6wh-rvv4</p>
<hr />
<p>Users who use Serve Static, Static Site Generation, Cookie utilities, or IP restriction middleware are strongly encouraged to upgrade to this version.</p>
<h2>v4.12.11</h2>
<h2>What's Changed</h2>
<ul>
<li>feat(css): add classNameSlug option to createCssContext by <a href="https://github.com/flow-pie"><code>@flow-pie</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/4834">honojs/hono#4834</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/flow-pie"><code>@flow-pie</code></a> made their first contribution in <a href="https://redirect.github.com/honojs/hono/pull/4834">honojs/hono#4834</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a href="https://github.com/honojs/hono/compare/v4.12.10...v4.12.11">https://github.com/honojs/hono/compare/v4.12.10...v4.12.11</a></p>
<h2>v4.12.10</h2>
<h2>What's Changed</h2>
<ul>
<li>test(router): fix <code>Simple capturing group</code> test by <a href="https://github.com/yusukebe"><code>@yusukebe</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/4838">honojs/hono#4838</a></li>
<li>docs: fix impaired -> inspired typo in benchmark READMEs by <a href="https://github.com/Abhi3975"><code>@Abhi3975</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/4843">honojs/hono#4843</a></li>
<li>fix(jsx/dom): apply select value after children are rendered by <a href="https://github.com/usualoma"><code>@usualoma</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/4847">honojs/hono#4847</a></li>
<li>fix(compress): convert strong ETag to weak ETag when compressing by <a href="https://github.com/usualoma"><code>@usualoma</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/4848">honojs/hono#4848</a></li>
<li>docs(ip-restriction): add clear JSDoc examples and param types by <a href="https://github.com/VISHNU7KASIREDDY"><code>@VISHNU7KASIREDDY</code></a> in <a href="https://redirect.github.com/honojs/hono/pull/4851">honojs/hono#4851</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/Abhi3975"><code>@Abhi3975</code></a> made their first contribution in <a href="https://redirect.github.com/honojs/hono/pull/4843">honojs/hono#4843</a></li>
<li><a href="https://github.com/VISHNU7KASIREDDY"><code>@VISHNU7KASIREDDY</code></a> made their first contribution in <a href="https://redirect.github.com/honojs/hono/pull/4851">honojs/hono#4851</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a href="c37ba26da9"><code>c37ba26</code></a> 4.12.12</li>
<li><a href="cc067c8559"><code>cc067c8</code></a> Merge commit from fork</li>
<li><a href="a586cd72e3"><code>a586cd7</code></a> Merge commit from fork</li>
<li><a href="48fa2233bc"><code>48fa223</code></a> Merge commit from fork</li>
<li><a href="b470278920"><code>b470278</code></a> Merge commit from fork</li>
<li><a href="9aff14bd72"><code>9aff14b</code></a> Merge commit from fork</li>
<li><a href="2c403c67eb"><code>2c403c6</code></a> 4.12.11</li>
<li><a href="f82aba8e8e"><code>f82aba8</code></a> feat(css): add classNameSlug option to createCssContext (<a href="https://redirect.github.com/honojs/hono/issues/4834">#4834</a>)</li>
<li><a href="9f374a55b2"><code>9f374a5</code></a> 4.12.10</li>
<li><a href="a8c56a6620"><code>a8c56a6</code></a> docs(ip-restriction): add clear JSDoc examples and param types (<a href="https://redirect.github.com/honojs/hono/issues/4851">#4851</a>)</li>
<li>Additional commits viewable in <a href="https://github.com/honojs/hono/compare/v4.12.9...v4.12.12">compare view</a></li>
</ul>
</details>
<br />
[](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)
Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/hyperdxio/hyperdx/network/alerts).
</details>
## Summary
Adds an MCP (Model Context Protocol) server to the HyperDX API, enabling AI assistants (Claude, Cursor, OpenCode, etc.) to query observability data, manage dashboards, and explore data sources directly via standardized tool calls.
Key changes:
- **MCP server** (`packages/api/src/mcp/`) — Streamable HTTP transport at `/api/mcp`, authenticated via Personal API Access Key
- **Tools** — `hyperdx_list_sources`, `hyperdx_query`, `hyperdx_get_dashboard`, `hyperdx_save_dashboard`, `hyperdx_delete_dashboard`, `hyperdx_query_tile`
- **Dashboard prompts** — Detailed prompt templates that guide LLMs in generating valid, high-quality dashboards
- **Shared logic** — Refactored dashboard validation/transformation out of the external API router into reusable utils (`packages/api/src/routers/external-api/v2/utils/dashboards.ts`)
- **Documentation** — `MCP.md` with setup instructions for Claude Code, OpenCode, Cursor, MCP Inspector, and other clients
- **Tests** — Unit tests for dashboard tools, query tools, tracing, and response trimming
### Screenshots
https://github.com/user-attachments/assets/8c5aa582-c79e-47e0-8f75-e03feabdf8a6
### How to test locally
1. Start the dev stack: `yarn dev`
2. Connect an MCP client (e.g. MCP Inspector):
```bash
cd packages/api && yarn dev:mcp
```
Then configure the inspector:
- **Transport Type:** Streamable HTTP
- **URL:** `http://localhost:8080/api/mcp`
- **Header:** `Authorization: Bearer <your-personal-access-key>`
- Click **Connect**
3. Alternatively, connect via Claude Code or OpenCode:
```bash
claude mcp add --transport http hyperdx http://localhost:8080/api/mcp \
--header "Authorization: Bearer <your-personal-access-key>"
```
4. Try listing sources, querying data, or creating/updating a dashboard through the connected AI assistant.
5. Run unit tests:
```bash
cd packages/api && yarn ci:unit
```
### References
- Linear Issue: HDX-3710
## Summary
Adds an `o` keybinding to the TUI that opens the currently expanded row in the HyperDX web app browser, deep-linking directly to the same view with the side panel open.
**Linear ticket:** https://linear.app/hyperdx/issue/HDX-3978
## What changed
When a user expands a row in the TUI (via `l`/Enter) and presses `o`, the browser opens to the HyperDX web app's `/search` page with URL parameters that reproduce the exact TUI state:
| URL Parameter | Description |
|---|---|
| `source` | Active source ID |
| `where` | User's search query + `TraceId:<id>` for traces |
| `from` / `to` | Current time range |
| `rowWhere` | SQL WHERE clause identifying the specific row (double-encoded) |
| `rowSource` | Source ID for the expanded row |
| `sidePanelTab` | Maps TUI tab: `overview` → `overview`, `columns` → `parsed`, `trace` → `trace` |
| `eventRowWhere` | Pre-selects the specific span in the trace waterfall (trace tab only, double-encoded JSON) |
### Tab behavior
- **Overview tab** → Opens side panel to Overview for the specific row
- **Column Values tab** → Opens side panel to Parsed (Column Values) for the specific row
- **Trace tab** → Opens side panel to Trace with waterfall, pre-selecting the highlighted span
### Source support
- **Trace sources**: URL includes `TraceId` filter combined with the user's search query
- **Log sources with trace correlation**: Full trace waterfall deep-linking supported
- **Log sources without trace**: Opens with the user's search query and row identification (no trace filter)
## Files changed
| File | Change |
|---|---|
| `packages/cli/src/utils/openUrl.ts` | **New** — Cross-platform browser open utility |
| `packages/cli/src/api/eventQuery.ts` | `buildFullRowQuery` returns `rowWhere` alongside `ChSql` |
| `packages/cli/src/components/EventViewer/useEventData.ts` | Exposes `expandedRowWhere` state |
| `packages/cli/src/components/TraceWaterfall/types.ts` | Added `onSelectedNodeChange` callback prop |
| `packages/cli/src/components/TraceWaterfall/TraceWaterfall.tsx` | Calls `onSelectedNodeChange` when selected span changes |
| `packages/cli/src/components/EventViewer/DetailPanel.tsx` | Threads `onTraceSelectedNodeChange` to TraceWaterfall |
| `packages/cli/src/components/EventViewer/types.ts` | Added `appUrl` to `EventViewerProps` |
| `packages/cli/src/components/EventViewer/EventViewer.tsx` | Threads `appUrl`, `expandedRowWhere`, `traceSelectedNode`, `submittedQuery` |
| `packages/cli/src/components/EventViewer/utils.ts` | Added `buildBrowserUrl` (full URL builder) and `buildSpanEventRowWhere` |
| `packages/cli/src/components/EventViewer/useKeybindings.ts` | Added `o` keybinding with all new params |
| `packages/cli/src/components/EventViewer/SubComponents.tsx` | Added `o` to help screen |
| `packages/cli/src/App.tsx` | Passes `appUrl` to EventViewer |
| `packages/cli/AGENTS.md` | Added `o` to keybindings table |
## Testing
- Type check: `npx tsc --noEmit` passes
- Lint: `yarn lint:fix` — 0 errors
## Summary
Optimizes the queries powering the event details panel and trace waterfall chart in the TUI, and improves the trace waterfall UX.
Fixes HDX-3963
Linear: https://linear.app/clickhouse/issue/HDX-3963
### Query optimizations
**Full row fetch (`SELECT *`)** — Removed the 1-year `dateRange` from `buildFullRowQuery`. The WHERE clause already uniquely identifies the row, so ClickHouse can use the filter directly without scanning time partitions. Matches the web frontend's `useRowData` pattern in `DBRowDataPanel.tsx`.
**Trace waterfall queries** — Replaced raw SQL builders (`buildTraceSpansSql`/`buildTraceLogsSql`) with `renderChartConfig`-based async builders. This enables:
- Time partition pruning via a tight ±1h `dateRange` derived from the event timestamp
- Materialized field optimization
- Query parameterization
**Trace span detail fetch** — Replaced raw SQL `SELECT * FROM ... WHERE ... LIMIT 1` with `renderChartConfig` via `buildTraceRowDetailConfig`, omitting `dateRange`/`timestampValueExpression` so ClickHouse uses the WHERE clause directly.
**Shared logic** — Extracted all trace config construction into `packages/cli/src/shared/traceConfig.ts`.
### UX improvements
**Trace Event Details → dedicated page** — Previously Event Details was rendered inline below the waterfall, consuming screen space. Now:
- Waterfall view gets the full terminal height
- Press `l`/`Enter` to drill into a span's full Event Details
- Press `h`/`Esc` to return to the waterfall
- `Ctrl+D/U` scrolls the detail view using full terminal height
**Trace waterfall scrolling** — Previously the waterfall truncated at `maxRows` with no way to see remaining spans. Now `j`/`k` scrolls the viewport when the cursor reaches the edge, with a scroll position indicator showing spans above/below.
### Files changed
| File | Change |
|---|---|
| `packages/cli/src/shared/traceConfig.ts` | NEW — shared trace config builders |
| `packages/cli/src/api/eventQuery.ts` | Replace raw SQL with `renderChartConfig`; remove date range from full row fetch |
| `packages/cli/src/components/TraceWaterfall/TraceWaterfall.tsx` | Split into waterfall + detail views; add scroll support |
| `packages/cli/src/components/TraceWaterfall/useTraceData.ts` | Use async builders + parameterized queries |
| `packages/cli/src/components/TraceWaterfall/types.ts` | Add `metadata`, `eventTimestamp`, `detailExpanded` props |
| `packages/cli/src/components/EventViewer/useKeybindings.ts` | Add `l`/`h` keybindings for trace detail navigation |
| `packages/cli/src/components/EventViewer/DetailPanel.tsx` | Thread `metadata`, `eventTimestamp`, `traceDetailExpanded` |
| `packages/cli/src/components/EventViewer/EventViewer.tsx` | Add `traceDetailExpanded` state; pass new props |
## Summary
This PR implements alerting on Raw SQL-based line/bar charts.
- This is only available for line/bar charts with this change. Number charts will be added in a future PR.
- The threshold is compared to the _last_ numeric column in each result.
- The interval parameter must be used. This is required for line charts to function, and is used for zero-fill and other functionality within the alerts task. This limitation will be removed for Number chart alerts when those are implemented.
- Start and and end date should be used, but are not required because there are some potential use-cases where they may not be desirable.
### Screenshots or video
https://github.com/user-attachments/assets/e2d0cd6c-b040-4490-89af-6a51a7380647
Logs from Check-Alerts evaluating a raw-sql alert
<img width="1241" height="908" alt="Screenshot 2026-04-09 at 3 01 14 PM" src="https://github.com/user-attachments/assets/dbed4e5f-bf27-4179-b8e0-897cc19f3d3a" />
### How to test locally or on Vercel
This must be tested locally, as alerts are not enabled in the preview environment.
<details>
<summary>Query for the "anomaly detection" example</summary>
```sql
WITH buckets AS (
SELECT
$__timeInterval(Timestamp) AS ts,
count() AS bucket_count
FROM $__sourceTable
WHERE TimestampTime >= fromUnixTimestamp64Milli({startDateMilliseconds:Int64}) - toIntervalSecond($__interval_s * 30) -- Fetch 30 intervals back
AND TimestampTime < fromUnixTimestamp64Milli({endDateMilliseconds:Int64})
AND SeverityText = 'error'
GROUP BY ts
ORDER BY ts
WITH FILL STEP toIntervalSecond($__interval_s)
),
anomaly_detection as (
SELECT
ts,
bucket_count,
avg(bucket_count) OVER (ORDER BY ts ROWS BETWEEN 30 PRECEDING AND 1 PRECEDING) as previous_30_avg, -- avg of previous 30 intervals
stddevPop(bucket_count) OVER (ORDER BY ts ROWS BETWEEN 30 PRECEDING AND 1 PRECEDING) as previous_30_stddev, -- standard deviation of previous 30 intervals
greatest(bucket_count - (previous_30_avg + 2 * previous_30_stddev), 0) as excess_over_2std -- compare bucket to avg + 2 stddev. clamp at 0.
FROM buckets
)
SELECT ts, excess_over_2std
FROM anomaly_detection
WHERE ts >= fromUnixTimestamp64Milli({startDateMilliseconds:Int64}) AND ts < fromUnixTimestamp64Milli({endDateMilliseconds:Int64})
```
</details>
### References
- Linear Issue: HDX-1605
- Related PRs:
## Summary
Upgrade all `@mantine/*` packages from v7.17.8 to v9.0.0 (skipping the v8 intermediate step since the breaking changes were manageable in a single pass). This improves React 19 compatibility and keeps the UI library current.
### Breaking changes resolved
**v7 → v8 changes:**
- `DateTimePicker`/`DateInput` `onChange` now returns a date string instead of a `Date` object — updated handlers in `AlertScheduleFields.tsx` and `SourceForm.tsx`
- Updated `postcss-preset-mantine` to ^1.18.0
**v8 → v9 changes:**
- `Collapse`: renamed `in` prop to `expanded` (11 instances across 10 files)
- `Grid`: removed deprecated `overflow="hidden"` prop (5 instances, 3 files) — v9 uses native CSS `gap` instead of negative margins
- `Text`/`Anchor`: renamed `color` prop to `c` style prop (7 instances, 5 files)
- `SourceSchemaPreview`: replaced removed `TextProps` `color` key with `React.CSSProperties`
- Theme: set `defaultRadius: 'sm'` in both theme configs to preserve existing visual appearance (v9 changed default from `sm` to `md`)
- Updated test for Collapse visibility behavior change in jsdom
### Not affected (verified)
- No `@mantine/carousel` or `@mantine/tiptap` usage — embla and Tiptap migrations not needed
- No `TypographyStylesProvider`, `Spoiler`, `positionDependencies`, `useFullscreen`, `useMouse`, `useMutationObserver`, or `zodResolver` from `@mantine/form` usage
- All `useLocalStorage` calls from `@mantine/hooks` already provide `defaultValue`
- `react-hook-form-mantine` has a peer dep mismatch (expects `@mantine/core ^7.0.0`) but its thin wrappers are compatible at runtime
### How to test locally or on Vercel
1. Start the dev stack with `yarn dev`
2. Navigate through key pages: search, dashboards, alerts, services dashboard, Kubernetes pages
3. Verify Collapse animations work correctly (alert details, nav sub-menus, advanced settings)
4. Verify Grid layouts render properly on services dashboard side panels
5. Verify no visual regressions in border-radius, spacing, or color across components
### References
- Linear Issue: https://linear.app/clickhouse/issue/HDX-3981/upgrade-mantine-v7-v9
- [Mantine v9 Changelog](https://mantine.dev/changelog/9-0-0/)
- [7.x → 8.x Migration Guide](https://mantine.dev/guides/7x-to-8x/)
- [8.x → 9.x Migration Guide](https://mantine.dev/guides/8x-to-9x/)
## Summary
Migrates the CLI from using API URLs (`-s, --server`) to app URLs (`-a, --app-url`), and adds interactive login prompts on expired/missing sessions.
Linear: https://linear.app/clickhouse/issue/HDX-3976
---
## Breaking Change
**The `-s` / `--server` flag has been removed.** All commands (except `upload-sourcemaps`) now use `-a` / `--app-url` instead.
The URL semantics have changed: users should now provide the **HyperDX app URL** (e.g. `http://localhost:8080`), not the API URL. The CLI derives the API URL internally by appending `/api`.
| Before | After |
|---|---|
| `hdx auth login -s http://localhost:8080/api` | `hdx auth login -a http://localhost:8080` |
| `hdx tui -s http://localhost:8080/api` | `hdx tui -a http://localhost:8080` |
| `hdx sources -s http://localhost:8080/api` | `hdx sources -a http://localhost:8080` |
> **Note:** `upload-sourcemaps` is unchanged — it still uses `--apiUrl` / `-u` as before.
**Existing saved sessions are auto-migrated** — old `session.json` files with `apiUrl` are converted to `appUrl` on first load.
---
## Changes
### `apiUrl` → `appUrl` migration
- `ApiClient` now accepts `appUrl` and derives `apiUrl` by appending `/api`
- `SessionConfig` stores `appUrl`; legacy sessions with `apiUrl` auto-migrate on load
- All commands use `-a, --app-url` instead of `-s, --server`
### Interactive login flow (HDX-3976)
- `hdx auth login` no longer requires `-a` — it prompts interactively for login method, app URL, then credentials
- Login method selector is extensible (currently Email/Password, designed for future OAuth support)
- **Expired sessions now prompt for re-login** instead of printing an error and exiting
- The app URL field is autofilled with the last used value so users can just hit Enter
- No longer requires manual deletion of `~/.config/hyperdx/cli/session.json` to recover from expired sessions
- Non-TUI commands (`sources`, `dashboards`, `query`) also launch interactive login on expired/missing sessions via `ensureSession()` helper
### TUI (`App.tsx`)
- Detects expired session on mount and shows "Session expired" message with editable URL field
- If the user changes the URL during re-login, the client is recreated
- 401/403 errors during data loading bounce back to the login screen instead of showing raw error messages
### Input validation & error handling
- App URL inputs are validated — rejects non-`http://` or `https://` URLs with a clear inline error
- `ApiClient.login()` catches network/URL errors and returns `false` instead of crashing
- `ApiClient.login()` verifies the session after a 302/200 response by calling `checkSession()` — prevents false "Logged in" messages from servers that return 302 without a valid session (e.g. SSO redirects)
- Login failure messages now mention both credentials and server URL
---
## Files changed
- `packages/cli/src/api/client.ts` — accepts `appUrl`, derives `apiUrl`, exposes `getAppUrl()`, login validation
- `packages/cli/src/utils/config.ts` — `SessionConfig.appUrl`, backward-compat migration
- `packages/cli/src/cli.tsx` — `-a` flag, `LoginPrompt`, `ReLoginPrompt`, `ensureSession()`, URL validation
- `packages/cli/src/App.tsx` — expired session detection, editable URL on re-login, 401/403 handling
- `packages/cli/src/components/LoginForm.tsx` — app URL prompt field, `message` prop, URL validation
## Summary
When `IS_LOCAL_MODE` sources are created (e.g. via "Connect to Demo Server" in the onboarding modal), IDs are now derived from a stable hash of the item content instead of `Math.random()`. This ensures every user who connects to the same demo server gets the same source IDs, making deep links with `?source=<id>` work across different users and sessions.
This has the added benefit of ensuring that the dashboards created in local mode will not break due to new sources being created on every new session.
Further, the `<OnboardingModal>` has been added to a few pages where it was previous missing, ensuring that users which are deeplinked to those pages see the modal and are able to create the demo connection.
### How to test locally or on Vercel
1. Open the play/demo environment in two separate browser profiles (or incognito windows) so each has fresh localStorage
2. In the first, connect to the demo server, then navigate to a page where a source is selected
3. Copy the link into the second browser. You should see the "connect to demo server" and once connected you should see the same source as was selected in the 1st browser.
### References
- Linear Issue: Closes HDX-3974
- Related PRs: none
The "Release CLI Binaries" job fails because `bun build --compile` can't resolve `@hyperdx/common-utils/dist/*` imports. In CI it's a fresh checkout — `yarn install` puts source code in place but `common-utils` needs to be compiled (tsup) to produce its `dist/` output.
Adds a `make ci-build` step between `yarn install` and the CLI compile step.
Fixes https://github.com/hyperdxio/hyperdx/actions/runs/24220292098/job/70710194631
## Summary
When a chart uses a trace source with a Duration Expression, the chart now automatically defaults to adaptive time unit formatting (e.g., `120.41s`, `45ms`, `3µs`) instead of requiring users to manually select a format. Users can still override the format through the existing display settings.
**Key changes:**
1. **New `duration` output type** in `NumberFormatSchema` — renders values adaptively as `µs`, `ms`, `s`, `min`, or `h` based on magnitude, instead of the clock-style `hh:mm:ss` format
2. **Auto-detection via exact match** — `getTraceDurationNumberFormat()` checks if any chart select `valueExpression` exactly equals the trace source's `durationExpression`. Only applies for unit-preserving aggregate functions (`avg`, `min`, `max`, `sum`, `quantile`, `any`, `last_value`, etc.) — skips `count` and `count_distinct`
3. **`useResolvedNumberFormat()` hook** — resolves the effective `numberFormat` for a chart: returns the user's explicit format if set, otherwise auto-detects duration format for trace sources
4. **UI form update** — Added "Duration" option to the number format selector with input unit picker (seconds/ms/µs/ns)
5. **Display settings drawer** — Shows the auto-detected format by default so users can see what's being applied
6. **Heatmap support** — Updated `DBHeatmapChart` tick formatter to use `formatDurationMs` for duration-formatted values
**Components updated:** `DBTimeChart`, `DBNumberChart`, `DBListBarChart`, `DBPieChart`, `DBTableChart`, `DBHeatmapChart`, `DBSearchHeatmapChart`, `DBEditTimeChartForm`, `ChartDisplaySettingsDrawer`
### How to test locally or on Vercel
1. Create a chart from a trace source with a unit-preserving aggFn (e.g., avg/p95/p99/min/max of the Duration column)
2. Verify the chart y-axis and tooltips now show values like `120.41s` or `45ms` instead of raw numbers
3. Open the chart display settings and verify the "Duration" output format is shown as the default
4. Change the aggFn to `count` or `count_distinct` — verify duration formatting is NOT applied
5. Change the format to something else (e.g., "Number") and verify the override persists
6. Switch back to "Duration" and pick different input units (seconds, ms, µs, ns) — the preview should update correctly
7. Check that non-trace-source charts are unaffected (no auto-detection triggers)
8. Verify the search heatmap chart for traces still shows proper duration labels
9. Reset to defaults in the display settings drawer and verify it returns to the auto-detected duration format
### References
- Linear Issue: HDX-3909
Linear Issue: [HDX-3909](https://linear.app/clickhouse/issue/HDX-3909/trace-duration-should-render-in-time-unit-by-default)
<div><a href="https://cursor.com/agents/bc-c39f9186-2593-4675-8f23-190cd148818b"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-c39f9186-2593-4675-8f23-190cd148818b"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
Co-authored-by: Cursor Agent <199161495+cursoragent@users.noreply.github.com>
## Summary
This PR:
- Updates the eslint config to restore `react-hook` plugin rules which were removed in #2069. Some of them were removed in without any replacements being _enabled_ in the recommended @eslint-react ruleset
- Reverts #2074, as those lint fixes were possible only because the related rules had been removed
Rule equivalence is documented here: https://www.eslint-react.xyz/docs/migrating-from-eslint-plugin-react-hooks
You can view current effective rule states with `cd packages/app && npx eslint --inspect-config`
### How to test locally or on Vercel
Locally:
```
make ci-lint / make dev-lint
```
### References
- Linear Issue: HDX-3467
- Related PRs:
## Summary
This PR extracts a TileAlertEditor component for future re-use in the Raw-SQL Alert UI. The UI has been updated to make the alert section collapsible and co-locate the "Remove Alert" button within the alert section. The collapsibility will be more important in the Raw SQL case, since the Raw SQL Editor is already pretty vertically tall.
### Screenshots or video
https://github.com/user-attachments/assets/4e595fc6-06f0-4ccd-ab1f-08dcb9895c89
### How to test locally or on Vercel
This must be tested locally, since alerts are not supported in local mode.
### References
- Linear Issue: Related to HDX-1605
- Related PRs:
## Summary
This PR fixes failing Saved-Search E2E tests by
1. Fixing a bug that caused the saved search's source to sometime not populate when navigating directly to the saved search
2. Updating the saved search modal to wait until the create saved search mutation completes and invalidates saved searches before attempting to navigate (also, added a loading state and error notifications to the modal)
3. Updated a few test assertions to be more explicit about waiting for the saved search to load
### Screenshots or video
### How to test locally or on Vercel
### References
- Linear Issue:
- Related PRs:
## Summary
Clicking a log/error event in the session replay event list either reopened the session replay instead of showing event details, or rendered the detail panel behind the replay drawer.
Fixed this by ensuring that isSubPanel is correctly set and using the ZIndexProvider to correctly stack the contexts.
## Steps to Reproduce
From the Sessions page:
1. Go to /sessions, select a session source, open a session card
2. In the session replay drawer, wait for the event list to load
3. Click any event row (e.g. a console.error)
4. Bug A: The detail panel opens behind the session replay drawer (overlay darkens but panel is inaccessible), or
ESC/close doesn't work correctly
From the Search page (URL conflict):
1. Go to /search, open any trace row to open the detail side panel
2. Click the Session Replay tab — this sets sidePanelTab=replay in the URL
3. In the session event list, click any event row
4. Bug B: The inner detail panel opens to the Session Replay tab again instead of event details (e.g. Overview/Trace)
## Summary
This PR fixes the time filter expression generated when a source's timestamp value expression includes a Date-type column. In such cases, the bounds should be inclusive for the date-type columns. This extends the fix from #1915 to date-type columns.
### Screenshots or video
#### Before - Histogram is empty because of exclusive end bounds
<img width="1758" height="700" alt="Screenshot 2026-04-07 at 12 32 05 PM" src="https://github.com/user-attachments/assets/95898655-21f1-4380-9b23-5333fcea007f" />
#### After - Histogram is populated
<img width="1762" height="717" alt="Screenshot 2026-04-07 at 12 30 25 PM" src="https://github.com/user-attachments/assets/d159b131-1f4f-44f8-982e-b26ca68835ff" />
### How to test locally or on Vercel
The unit tests demonstrate the fix.
<details>
<summary>Alternatively, create a table with the following schema and data</summary>
```sql
CREATE TABLE default.otel_logs_date_pk
(
`Timestamp` DateTime64(9) CODEC(Delta(8), ZSTD(1)),
`TimestampTime` DateTime DEFAULT toDateTime(Timestamp),
`TimestampDate` Date DEFAULT toDate(Timestamp),
`TraceId` String CODEC(ZSTD(1)),
`SpanId` String CODEC(ZSTD(1)),
`TraceFlags` UInt8,
`SeverityText` LowCardinality(String) CODEC(ZSTD(1)),
`SeverityNumber` UInt8,
`ServiceName` LowCardinality(String) CODEC(ZSTD(1)),
`Body` String CODEC(ZSTD(1)),
`ResourceSchemaUrl` LowCardinality(String) CODEC(ZSTD(1)),
`ResourceAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
`ScopeSchemaUrl` LowCardinality(String) CODEC(ZSTD(1)),
`ScopeName` String CODEC(ZSTD(1)),
`ScopeVersion` LowCardinality(String) CODEC(ZSTD(1)),
`ScopeAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
`LogAttributes` Map(LowCardinality(String), String) CODEC(ZSTD(1)),
`__hdx_materialized_k8s.cluster.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.cluster.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.container.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.container.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.deployment.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.deployment.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.namespace.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.namespace.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.node.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.node.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.pod.name` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.pod.name'] CODEC(ZSTD(1)),
`__hdx_materialized_k8s.pod.uid` LowCardinality(String) MATERIALIZED ResourceAttributes['k8s.pod.uid'] CODEC(ZSTD(1)),
`__hdx_materialized_deployment.environment.name` LowCardinality(String) MATERIALIZED ResourceAttributes['deployment.environment.name'] CODEC(ZSTD(1)),
INDEX idx_trace_id TraceId TYPE bloom_filter(0.001) GRANULARITY 1,
INDEX idx_res_attr_key mapKeys(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_res_attr_value mapValues(ResourceAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_scope_attr_key mapKeys(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_scope_attr_value mapValues(ScopeAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_log_attr_key mapKeys(LogAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_log_attr_value mapValues(LogAttributes) TYPE bloom_filter(0.01) GRANULARITY 1,
INDEX idx_lower_body lower(Body) TYPE tokenbf_v1(32768, 3, 0) GRANULARITY 8
)
ENGINE = MergeTree
PARTITION BY toDate(TimestampTime)
PRIMARY KEY (TimestampDate, ServiceName, TimestampTime)
ORDER BY (TimestampDate, ServiceName, TimestampTime, Timestamp)
TTL TimestampTime + toIntervalDay(1)
SETTINGS index_granularity = 8192, ttl_only_drop_parts = 1;
insert into default.otel_logs_date_pk (Timestamp, SeverityText, Body) VALUES (now(), 'info', 'message');
insert into default.otel_logs_date_pk (Timestamp, SeverityText, Body) VALUES (now()-interval 1 minute, 'info', 'message');
```
Then create a source that points to that table with the timestampValueExpr `TimestampTime, TimestampDate`
</details>
### References
- Linear Issue: Closes HDX-3930
- Related PRs:
## Why
Users had no in-app list of keyboard shortcuts. Documenting them under **Help** makes discoverability match other support links and reduces guesswork (e.g. command palette vs search bar).
<img width="467" height="201" alt="Screenshot 2026-04-07 at 17 23 40" src="https://github.com/user-attachments/assets/f03e3379-0feb-4912-8f3a-89fca338d02a" />
<img width="901" height="1003" alt="Screenshot 2026-04-07 at 17 23 46" src="https://github.com/user-attachments/assets/d3f7bb0f-3a10-44c2-8e5f-7259382652b8" />
## What
- **Help → Keyboard shortcuts** opens a modal with shortcuts gathered from current hotkey usage (Spotlight ⌘/Ctrl+K, `/` and `s` for search/WHERE, time picker, find-in-table, log navigation, trace timeline, dashboards, etc.).
- Help dropdown order: documentation and setup first, then shortcuts, then Discord.
- Modal: comfortable width, dividers between rows, **or** vs **+** for alternatives vs chords.
- E2E: help menu includes the new item and opens the modal.
- Changeset: `@hyperdx/app` patch.
## Test plan
- [ ] Open Help → Keyboard shortcuts; confirm list and close behavior.
- [ ] CI: rely on PR checks (`make ci-lint` / `make ci-unit` if running locally).
## Summary
This PR updates dashboard tiles so that
1. When a tile references a source that no longer exists, there is an appropriate error message
2. When a tile references a source that no longer exists, the user is able to click the edit tile button to fix the issue
### Screenshots or video
<img width="887" height="429" alt="Screenshot 2026-04-07 at 9 40 53 AM" src="https://github.com/user-attachments/assets/ae0f77bc-3fcc-40c3-bf65-9ed454f31a4b" />
### How to test locally or on Vercel
This can be tested in the preview environment by creating a tile and then deleting the associated source.
### References
- Linear Issue: HDX-3926
- Related PRs:
## Summary
- Shows query errors in search page event patterns in the same way as for event deltas.
- Previously, a loading state was shown indefinitely if there was an error.
### Screenshots or video
<img width="1375" height="554" alt="Screenshot 2026-04-07 at 16 14 37" src="https://github.com/user-attachments/assets/25417f1a-bfd3-44ca-bcd6-aa24156fad14" />
### How to test locally or on Vercel
1. Easiest to test locally by manually throwing from the `useQueriedChartConfig` query function.
### References
- Linear Issue: Closes HDX-3933
- Related PRs:
## Summary
This PR adds alert icons to the dashboard page, matching the implementation from the search page. Similarly, alerts icons have been added to favorited dashboards in the sidebar.
### Screenshots or video
<img width="1257" height="796" alt="Screenshot 2026-04-03 at 3 05 42 PM" src="https://github.com/user-attachments/assets/9e3fe31d-b757-46e8-8034-9be80529c96e" />
<img width="245" height="353" alt="Screenshot 2026-04-03 at 3 17 54 PM" src="https://github.com/user-attachments/assets/d7b06536-646d-4bd6-950c-b9087c3b3dbd" />
### How to test locally or on Vercel
This can be tested locally by creating some dashboards, favoriting them, and creating alerts on those dashboards.
### References
- Linear Issue: Closes HDX-3921
- Related PRs:
## Summary
This PR adds an error message on the DB Infra Panel when the selected source is missing a correlated metric source.
Previously, this case would have just resulted in an empty page, with no indication of what was wrong.
### Screenshots or video
<img width="2038" height="367" alt="Screenshot 2026-04-03 at 8 28 22 AM" src="https://github.com/user-attachments/assets/4fde26c6-5ea8-4cf8-bdfa-9028ae48b15e" />
### How to test locally or on Vercel
This can be tested locally by creating a source that points to the demo dataset, without a correlated metric source (I suggest the demo dataset because it has K8s metrics).
### References
- Linear Issue:
- Related PRs:
## Summary
- Adds a retry loop (3 attempts, 10s backoff) for `docker compose pull` before `docker compose up -d` in the E2E test workflow
- Fixes transient Docker Hub auth timeout failures where pulling `mongo:5.0.32-focal` failed with `context deadline exceeded`
- Only the E2E workflow is affected; no other CI workflows use `docker compose up`
## Failed run
https://github.com/hyperdxio/hyperdx/actions/runs/23953878483/job/69867599721
## Summary
This PR fixes a bug that caused alerts created on other dashboards to be displayed on tiles with IDs that match the other dashboard. This in turn led to failures updating the alert on the "duplicate" dashboard.
The included integration test demonstrates the case.
### Screenshots or video
### How to test locally or on Vercel
### References
- Linear Issue: Closes HDX-3918
- Related PRs:
## Summary
This PR
1. Replaces `eslint-config-react` with the modernized equivalent `@eslint-react/eslint-plugin`. Note that `eslint-config-react` was not actually enabled previously, as none of its rules were added to the eslint config.
2. Enables the `no-unstable-default-props` rule and fixes existing violations. This rule catches components with optional props of unstable types (objects, arrays) which have a default value assigned. The default value changes on each render, which can cause unecessary re-renders and re-computations of useMemos and useEffects.
We can enable more rules from this plugin in future PRs.
### Screenshots or video
There are no expected behavior changes
### How to test locally or on Vercel
The app can be regression tested in the preview environment.
### References
- Linear Issue: Closes HDX-3809
- Related PRs:
## Summary
Updates the chart explorer page so that the chart query is executed automatically once on page load instead of waiting for the user to click the run button.
The motivation is external tools that translate queries from other systems into HyperDX chart configs and deeplink the user into the `/chart` page. Today the user lands on a fully-populated form but still has to click once to see results; with this change the chart renders immediately.
The form gains an `autoRun` prop. When true, a latched effect calls `onSubmit()` exactly once after the source data has loaded, so form validation has the table metadata it needs.
### How to test locally or on Vercel
1. `yarn dev` in `packages/app`
2. Open `/chart?config=%7B%22name%22%3A%22%22%2C%22select%22%3A%5B%7B%22aggFn%22%3A%22count%22%2C%22aggCondition%22%3A%22%22%2C%22aggConditionLanguage%22%3A%22sql%22%2C%22valueExpression%22%3A%22%22%7D%5D%2C%22where%22%3A%22%22%2C%22whereLanguage%22%3A%22sql%22%2C%22displayType%22%3A%22line%22%2C%22granularity%22%3A%22auto%22%2C%22source%22%3A%22<your-source-id>%22%7D` — replace `<your-source-id>` with a real source id from your instance
3. The chart should render results without clicking the run button
## Summary
Fixes a bug where search page filters were silently hidden when their values contained special SQL keywords, operators, or escaped quotes inside single-quoted strings. The parsing functions in `searchFilters.tsx` used naive `.includes()` and `.split()` calls that matched these patterns even inside quoted values.
**Bugs fixed:**
- `extractInClauses()` skipped valid IN clauses when quoted values contained `=`, `>`, `<`, or ` OR ` (e.g., `Body IN ('key=value')` was hidden)
- `parseQuery()` misinterpreted IN clauses as BETWEEN ranges when quoted values contained ` BETWEEN ` (e.g., `Body IN ('I AM BETWEEN THE HEDGES')`)
- `extractInClauses()` misidentified IN clauses as NOT IN exclusions when quoted values contained ` NOT IN ` (e.g., `Body IN ('this is NOT IN scope')`)
- All four quote-tracking loops only handled `\'` escaping, not the SQL-standard `''` escaping used by ClickHouse (e.g., `Body IN ('it''s a test')` would break quote tracking)
**Fix:**
- Added `containsOutsideQuotes()` — checks for keywords/operators while respecting single-quoted string boundaries
- Added `splitOnFirstOutsideQuotes()` — splits on the first unquoted occurrence of a delimiter
- Added `isQuoteBoundary()` / `handleQuoteEscape()` — shared helpers for quote-tracking that handle both `''` (SQL standard) and `\'` (backslash) escaping
- Updated `getBooleanOrUnquotedString()` to un-escape `''` → `'` in parsed values
**Tests**: 12 new unit tests (45 total) covering all edge cases.
### How to test locally or on Vercel
1. Navigate to the search page
2. Apply a filter on a value that contains `=`, `>`, `<`, `OR`, `BETWEEN`, `NOT IN`, or escaped quotes (e.g., `Body IN ('key=value')`, `Body IN ('it''s a test')`)
3. Verify the filter appears as checked in the sidebar — previously it would be silently hidden
### References
- Linear Issue: HDX-3901
Linear Issue: [HDX-3901](https://linear.app/clickhouse/issue/HDX-3901/hidden-filters-bug)
<div><a href="https://cursor.com/agents/bc-2b1e64da-d6df-4cd7-b894-43675c312e34"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-2b1e64da-d6df-4cd7-b894-43675c312e34"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>
Co-authored-by: Cursor Agent <199161495+cursoragent@users.noreply.github.com>
## Summary
Previously array indexes were treated as map lookups, which caused them to be wrapped in quotations. This fixes array index query rendering by handling array indexes.
### References
- Related PRs: Closes https://github.com/hyperdxio/hyperdx/issues/1863
## Summary
Fixes a query error when clicking Search, Add to Filters, Chart, or Column on nested JSON values inside Map column attributes (e.g., LogAttributes['config'] containing '{"host": "localhost"}').
buildJSONExtractQuery was using only the last element of parsedJsonRootPath as the base column, producing invalid ClickHouse expressions like JSONExtractString(config, 'host') instead of JSONExtractString(LogAttributes['config'], 'host'). Fixed by using mergePath to construct the full column path and passing jsonColumns through so both Map (bracket notation) and JSON (dot notation) columns are handled correctly.
## How to test locally or on Vercel
Open a log or trace side panel where a Map column attribute (e.g., LogAttributes) has a value containing a JSON string (e.g., {"host": "localhost", "port": 5432})
Expand the JSON value in the side panel by clicking "Expand JSON"
Hover over a nested key (e.g., host) and click Search — verify the search page opens without a query error and the WHERE clause uses the full column path (e.g., JSONExtractString(LogAttributes['config'], 'host') = 'localhost')
Repeat step 3 with Add to Filters, Column, and Chart (for numeric values) to verify all actions produce valid queries
## References
Linear Issue: Fixes [HDX-3906](https://linear.app/clickhouse/issue/HDX-3906/clicking-search-on-nested-logattributes-map-key-results-in-query-error)