## 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)
## Summary
This PR
1. Updates the Saved Searches and Dashboards pages to group objects by tag when in the card view
2. Fixes a display bug on the sidebar which resulted in favorited saved searches with long names and alerts configured to wrap
3. Adds the "Saved searches and dashboards have moved!" message back to the sidebar - it was inadvertently removed in a previous PR.
### Screenshots or video
<img width="1749" height="1024" alt="Screenshot 2026-04-01 at 3 46 42 PM" src="https://github.com/user-attachments/assets/b5f03bcb-7588-47cb-acc5-af56f0f9ddf4" />
### How to test locally or on Vercel
This can be tested in the preview environment by creating and tagging some saved searches and dashboards.
### References
- Linear Issue:
- Related PRs:
## Summary
- `yarn app:dev:local` was broken because it didn't source `scripts/dev-env.sh`, so port variables (`HYPERDX_APP_PORT`, `HYPERDX_API_PORT`, etc.) referenced in `.env.development` were undefined
- This caused `dotenv-expand` to hit infinite recursion (stack overflow) when trying to interpolate the unset variables
- Now `app:dev:local` sources `dev-env.sh`, builds common-utils, and tees logs — matching the pattern used by `yarn dev`
## Summary
Adds ability to copy JSON button for entire row
### Screenshots or video
https://github.com/user-attachments/assets/0a30db15-6db0-4858-8503-d1b1688615ca
### How to test locally or on Vercel
1. Go to vercel preview
2. Click on any row
3. Copy to clipboard by clicking copy icon
### References
- Linear Issue: Closes HDX-3900
## Summary
This PR changes searches to a 1 minute up front window, rather than a 6h. Also, the refresh time was moved from 4s back to 10s.
### How to test locally or on Vercel
1. Go to preview
2. Inspect console and check a search query
3. See search query is searching over 60 seconds
### References
Closes HDX-3866
## Summary
- Adds an "Open ClickHouse" to the dev portal that opens the ClickHouse web UI.
### Screenshots or video
<img width="334" height="168" alt="Screenshot 2026-04-02 at 12 29 59" src="https://github.com/user-attachments/assets/1482044a-f7ff-4b42-a05e-49fd6b4f50ca" />
### How to test locally or on Vercel
1. Run the dev portal.
2. Check the "Open ClickHouse" button appears for all instances of the stack, and opens the web UI when clicked.
## Summary
Filter values containing single quotes (e.g. `my 'filter' key`) produced invalid SQL because the quotes were not escaped when building `IN`/`NOT IN` clauses in the search page sidebar filters. This caused broken queries and prevented users from filtering on values that contain single quotes.
The fix applies SQL-standard quote escaping (`'` → `''`) in four places within `searchFilters.tsx`:
- **`filtersToQuery`**: Escapes `'` as `''` when wrapping string values in SQL single quotes for `IN`/`NOT IN` conditions
- **`splitValuesOnComma`**: Recognizes `''` as an escaped quote inside quoted strings, instead of treating it as a string boundary
- **`getBooleanOrUnquotedString`**: Unescapes `''` back to `'` when stripping surrounding quotes from parsed values
- **`extractInClauses`**: Handles `''` correctly when splitting conditions on `AND` inside quoted strings
This fix also applies to dashboard filters since `useDashboardFilters.tsx` imports the same `filtersToQuery`/`parseQuery` functions.
### How to test locally or on Vercel
1. Open the search page and add a sidebar filter whose value contains single quotes (e.g. filter on `message = "my 'filter' key"`)
2. Confirm the filter is applied correctly and the query executes without errors
3. Remove and re-add the filter to confirm round-trip parsing works (filter state is correctly restored from the SQL condition)
### References
- Linear Issue: HDX-3902
Linear Issue: [HDX-3902](https://linear.app/clickhouse/issue/HDX-3902/filter-keys-dont-support-quotes)
<div><a href="https://cursor.com/agents/bc-bbd50ba9-f23a-4c6f-989e-b4ff6f9e81d5"><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-bbd50ba9-f23a-4c6f-989e-b4ff6f9e81d5"><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
On smaller screens (e.g. half MacBook screen), the search WHERE input on the search page was shrinking to be unusably small. This happened because the WHERE input (`flex: 1 1 0%`, `min-width: 0`) competed with fixed-width siblings — the TimePicker (`w={350}`), optional live-tail interval select (80px, `flexShrink: 0`), and submit button (`flexShrink: 0`) — inside a non-wrapping flex row.
**Fix:**
`DBSearchPage.tsx`:
- Added `wrap="wrap"` to the outer flex container so items can flow to the next line when space is insufficient
- Set `minWidth="min(600px, 100%)"` on `SearchWhereInput` so it wraps to full width at 600px, while capping at 100% on very narrow viewports to prevent horizontal overflow
- Grouped TimePicker, live tail select, and Run button in a flexible inner `Flex` (`flex: 0 1 500px`, `minWidth: 0`) so they wrap together and can shrink to fit
- TimePicker sits in a `Box` with `minWidth: 100` so it compresses on narrow screens while live tail select and Run button keep their fixed sizes
`TimePicker.tsx`:
- Added `width` prop (defaults to `350` for backward compatibility) so the search page can pass `width="100%"` to make TimePicker fill its flexible container
`SearchPage.ts` (E2E page object):
- Fixed flaky tests by pressing Escape after filling the search input to dismiss the autocomplete suggestions dropdown before clicking the submit button
**Behavior:**
- **Wide screens**: WHERE input and controls share a row as before, TimePicker stays at 350px
- **Narrow screens**: WHERE input takes full width on its own row; TimePicker compresses to fit alongside live tail select and Run button without overflow
- **Very narrow screens (<600px viewport)**: Everything still fits without horizontal overflow
### How to test locally or on Vercel
1. Navigate to the Search page (`/search`)
2. Resize the browser window to be narrow (e.g. half of a MacBook screen, ~700px wide)
3. Verify the WHERE input expands to full width on its own row
4. Verify TimePicker, live tail select, and Run button all fit on the second row without horizontal overflow
5. Resize even narrower and verify TimePicker compresses while the Run button remains visible
6. At wider widths, verify the WHERE input and controls remain on the same row as before
### References
- Linear Issue: HDX-3903
Linear Issue: [HDX-3903](https://linear.app/clickhouse/issue/HDX-3903/on-smaller-screens-on-the-search-page-the-search-where-input-shrinks)
<div><a href="https://cursor.com/agents/bc-dbc6584f-4844-40c4-9649-66a94ef7b017"><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-dbc6584f-4844-40c4-9649-66a94ef7b017"><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
Fix ClickHouse query error when expanding log rows with Nullable(DateTime64) columns (and other Nullable types).
- The `convertCHDataTypeToJSType` function didn't generically unwrap `Nullable(...)` types, so `Nullable(DateTime64(...))` fell through to the default string comparison instead of using `parseDateTime64BestEffort()`
- Added general `Nullable(...)` recursive unwrapping (matching the existing `LowCardinality(...)` pattern)
- Hoisted null value handling above the type switch in `processRowToWhereClause` so all column types (Date, Array, Map, etc.) correctly emit `isNull()` for null values
### Screenshots or video
N/A — no UI changes.
### How to test locally or on Vercel
1. Set up a ClickHouse table with a `Nullable(DateTime64)` column and ingest some rows (including rows with null values in that column).
2. Open the log explorer and expand a row that has a `Nullable(DateTime64)` column.
3. Verify that clicking into the row no longer returns a 400 error.
4. Verify that clicking into a row where the `Nullable(DateTime64)` column is null correctly filters using `isNull()`.
### References
- Related PRs:
---
📍 Connect Copilot coding agent with [Jira](https://gh.io/cca-jira-docs), [Azure Boards](https://gh.io/cca-azure-boards-docs) or [Linear](https://gh.io/cca-linear-docs) to delegate work to Copilot in one click without leaving your project management tool.
## Summary
- Fix dev environment port isolation so OTel collector, MongoDB, and ClickHouse resolve to the correct worktree-specific ports
- Add slot-specific session cookies so multiple worktrees on localhost don't interfere with each other's login sessions
- Share NX build cache across worktrees for faster builds
- Add worktrunk project config for automated worktree lifecycle management
## Changes
### Port resolution fix
**Root `.env`**: Replace self-referential `HDX_DEV_*` vars (e.g. `HDX_DEV_OTEL_HTTP_PORT=${HDX_DEV_OTEL_HTTP_PORT:-4318}`) with plain default values. The `${VAR:-default}` syntax caused `dotenv-expand` infinite recursion, preventing `dev-env.sh` port overrides from taking effect.
**`packages/api/.env.development`**: Remove redundant self-referential port declarations and `:-default` fallbacks from `${HDX_DEV_*}` references. Ports now come exclusively from `dev-env.sh` exports or root `.env` defaults.
**`packages/app/.env.development`**: Set `HYPERDX_API_KEY` to `super-secure-ingestion-api-key` (matching the API/collector) instead of a placeholder. Remove self-referential port declarations.
**`packages/app/src/config.ts`**: Add `process.env.OTEL_EXPORTER_OTLP_ENDPOINT` as fallback for `HDX_COLLECTOR_URL` so the browser OTel SDK picks up the correct collector endpoint.
### Session cookie isolation
**`packages/api/src/api-app.ts`**: Use `connect.sid.<slot>` as the session cookie name in dev mode so multiple worktrees on `localhost` maintain independent sessions. Guarded behind `config.IS_DEV && process.env.HDX_DEV_SLOT` — production uses the default `connect.sid`.
### Shared NX build cache
**`scripts/dev-env.sh`**: Set `NX_CACHE_DIRECTORY=~/.config/hyperdx/nx-cache` so all worktrees share a single content-hash-based build cache. Unchanged packages get cache hits regardless of worktree; changed packages rebuild correctly.
### Worktrunk project config
**`.config/wt.toml`**: New project config for [worktrunk](https://worktrunk.dev) (`wt`) worktree lifecycle hooks:
- `pre-start`: Symlink `node_modules/` from primary worktree (instant, no copy)
- `post-start`: Copy `.env.local` from primary worktree
- `post-remove`: Tear down Docker stacks (`dev-down`, `dev-int-down`, `dev-e2e-down`) for the removed worktree's slot
## Summary
This PR adds a gallery of importable dashboard templates to the dashboards page. The existing Dashboard import functionality is modified to support importing dashboard templates which are included in the app source code bundle.
### Screenshots or video
https://github.com/user-attachments/assets/eae37214-f012-44dd-83ef-086749846260
### How to test locally or on Vercel
This can be tested as shown above in the preview environment.
### References
- Linear Issue: Closes HDX-3661 Closes HDX-3814
- Related PRs:
## Summary
This PR adds the source schema preview to a few additional places where it is likely to be helpful:
1. SQL Chart Editor
2. Trace Panel
### Screenshots or video
<img width="2300" height="1281" alt="Screenshot 2026-04-01 at 11 07 42 AM" src="https://github.com/user-attachments/assets/cc85c9cf-676e-43c1-804a-501565f80153" />
<img width="2050" height="1284" alt="Screenshot 2026-04-01 at 11 07 28 AM" src="https://github.com/user-attachments/assets/a6efafd2-c18f-4ead-999c-047261864810" />
### How to test locally or on Vercel
This can be tested in the preview environment
### References
- Linear Issue: Closes HDX-3868
- Related PRs:
## Summary
Simplify the knip GitHub Action and expand where it runs:
- **Remove main-branch comparison** — knip now reports all issues found on the current branch rather than diffing against main. This makes the output more straightforward and actionable.
- **Fail on errors** — the job calls `core.setFailed()` when knip finds issues, so the check shows as failed on the PR.
- **Run on push to main** — added `push` trigger so knip also runs when commits land on main.
- **Support fork PRs** — on fork PRs, the GITHUB_TOKEN can't write to the base repo, so the action skips PR commenting and logs results to the Actions console instead.
### How to test locally or on Vercel
1. Open a PR from a non-fork branch — knip should comment on the PR with results
2. Push to main — knip should run and log results to the Actions console without attempting to comment
3. (Fork PR) — knip should run, log results to the console, and skip commenting
### References
- Related PRs:
## Summary
This PR adds per-user favorites for dashboards and saved searches. Users can favorite dashboards or saved searches to see them at the top of the relevant listing page and in the sidebar.
The favorites are persisted in Mongo or (in local mode) in local storage.
### Screenshots or video
https://github.com/user-attachments/assets/7cc273df-9fd8-4abb-bed3-5df742442ab3
### How to test locally or on Vercel
The local mode favorites can be tested in vercel preview. The mongodb-backed favorites can be tested locally.
### References
- Linear Issue: Closes HDX-3455
- Related PRs:
## Summary
This PR adds a $__sourceTable macro which is replaced (in Raw SQL-based charts) with the selected source (if there is one). This allows for easier import/export when using raw SQL charts, since sources mapped during import will automatically be replaced with the correct source table name when queried.
`$__sourceTable` also supports arguments for the metric tables, eg. `$__sourceTable(sum)`.
### Screenshots or video
<img width="1439" height="1151" alt="Screenshot 2026-03-31 at 3 07 43 PM" src="https://github.com/user-attachments/assets/bdbaa7fb-0570-46bc-90c5-032e3d64fd34" />
### How to test locally or on Vercel
This can all be tested in the preview environment.
### References
- Linear Issue: Closes HDX-3834
- Related PRs:
## Summary
In response to the recent [axios supply chain attack](https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan), we are tightening package management controls to reduce our exposure to malicious or compromised npm packages.
**Changes:**
- Updated `yarnPath` in `.yarnrc.yml` to point to Yarn 4.13.0
- Updated `packageManager` in `package.json` to reflect Yarn 4.13.0
- Removed old Yarn releases (4.5.1 and 1.22.18) from the `releases/` directory
- Added Yarn 4.13.0 to the `releases/` directory
- Set `npmMinimalAgeGate: 7` in `.yarnrc.yml` — Yarn will now block installation of any package version published less than 7 days ago, providing a buffer against freshly-injected malicious releases
### How to test locally or on Vercel
1. Pull this branch and run `yarn --version` — confirm it outputs `4.13.0`.
2. Run `yarn install` and verify it completes without errors.
3. Attempt to add a package version published within the last 7 days (e.g. a freshly released patch) and confirm Yarn rejects it with an age gate error.
4. Add a package version older than 7 days and confirm it installs successfully.
5. Confirm the old Yarn release files (`4.5.1`, `1.22.18`) are no longer present in `releases/`.
### References
- Blog post: [axios compromised on npm — malicious versions drop remote access trojan](https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan)
## Summary
- Isolate dev, E2E, and integration test environments so multiple git worktrees can run all three simultaneously without port conflicts
- Each worktree gets a deterministic slot (0-99) with unique port ranges: dev (30100-31199), E2E (20320-21399), CI integration (14320-40098)
- Dev portal dashboard (http://localhost:9900) auto-discovers all running stacks, streams logs, and provides a History tab for past run logs
## Port Isolation
| Environment | Port Range | Project Name |
|---|---|---|
| Dev stack | 30100-31199 | `hdx-dev-<slot>` |
| E2E tests | 20320-21399 | `e2e-<slot>` |
| CI integration | 14320-40098 | `int-<slot>` |
All three can run simultaneously from the same worktree with zero port conflicts.
## Dev Portal Features
**Live tab:**
- Auto-discovers dev, E2E, and integration Docker containers + local services (API, App)
- Groups all environments for the same worktree into a single card
- SSE log streaming with ANSI color rendering, capped at 5000 lines
- Auto-starts in background from `make dev`, `make dev-e2e`, `make dev-int`
**History tab:**
- Logs archived to `~/.config/hyperdx/dev-slots/<slot>/history/` on exit (instead of deleted)
- Each archived run includes `meta.json` with worktree/branch metadata
- Grouped by worktree with collapsible cards, search by worktree/branch
- View any past log file in the same log panel, delete individual runs or clear all
- Custom dark-themed confirm modal (no native browser dialogs)
## What Changed
- **`scripts/dev-env.sh`** — Slot-based port assignments, portal auto-start, log archival on exit
- **`scripts/test-e2e.sh`** — E2E port range (20320-21399), log capture via `tee`, portal auto-start, log archival
- **`scripts/ensure-dev-portal.sh`** — Shared singleton portal launcher (works sourced or executed)
- **`scripts/dev-portal/server.js`** — Discovery for dev/E2E/CI containers, history API (list/read/delete), local service port probing
- **`scripts/dev-portal/index.html`** — Live/History tabs, worktree-grouped cards, search, collapse/expand, custom confirm modal, ANSI color log rendering
- **`docker-compose.dev.yml`** — Parameterized ports/volumes/project name with `hdx.dev.*` labels
- **`packages/app/tests/e2e/docker-compose.yml`** — Updated to new E2E port defaults
- **`Makefile`** — `dev-int`/`dev-e2e` targets with log capture + portal auto-start; `dev-portal-stop`; `dev-clean` stops everything + wipes slot data
- **`.env` files** — Ports use `${VAR:-default}` syntax across dev, E2E, and CI environments
- **`agent_docs/development.md`** — Full documentation for isolation, port tables, E2E/CI port ranges
## How to Use
```bash
# Start dev stack (auto-starts portal)
make dev
# Run E2E tests (auto-starts portal, separate ports)
make dev-e2e FILE=navigation
# Run integration tests (auto-starts portal, separate ports)
make dev-int FILE=alerts
# All three can run simultaneously from the same worktree
# Portal at http://localhost:9900 shows everything
# Stop portal
make dev-portal-stop
# Clean up everything (all stacks + portal + history)
make dev-clean
```
## Dev Portal
<img width="1692" height="944" alt="image" src="https://github.com/user-attachments/assets/6ed388a3-43bc-4552-aa8d-688077b79fb7" />
<img width="1689" height="935" alt="image" src="https://github.com/user-attachments/assets/8677a138-0a40-4746-93ed-3b355c8bd45e" />
## Test Plan
- [x] Run `make dev` — verify services start with slot-assigned ports
- [x] Run `make dev` in a second worktree — verify different ports, no conflicts
- [x] Run `make dev-e2e` and `make dev-int` simultaneously — no port conflicts
- [x] Open http://localhost:9900 — verify all stacks grouped by worktree
- [x] Click a service to view logs — verify ANSI colors render correctly
- [x] Stop a stack — verify logs archived to History tab with correct worktree
- [x] History tab — search, collapse/expand, view archived logs, delete
- [x] `make dev-clean` — stops everything, wipes slot data and history
## Summary
This PR moves saved searches from the sidebar to a new Saved Search listing page, for consistency with the new dashboards listing page.
### Screenshots or video
https://github.com/user-attachments/assets/11afec45-2a50-4f52-aad7-9a441ac115f5
### How to test locally or on Vercel
This can be tested in the preview environment (but the alert indicator will only work if you run it locally, not in LOCAL_MODE).
### References
- Linear Issue: Closes HDX-3833 Closes HDX-2066 Closes HDX-2633
- Related PRs: