diff --git a/.changeset/collapsible-filter-sidebar.md b/.changeset/collapsible-filter-sidebar.md
new file mode 100644
index 00000000..de1aec7e
--- /dev/null
+++ b/.changeset/collapsible-filter-sidebar.md
@@ -0,0 +1,5 @@
+---
+"@hyperdx/app": patch
+---
+
+feat: Add collapsible filter sidebar toggle to search page
diff --git a/agent_docs/code_style.md b/agent_docs/code_style.md
index 6d42f333..70ef10ed 100644
--- a/agent_docs/code_style.md
+++ b/agent_docs/code_style.md
@@ -37,13 +37,25 @@ The project uses Mantine UI with **custom variants** defined in `packages/app/sr
| `variant="secondary"` | Secondary actions (Cancel, Clear, auxiliary actions) | `` |
| `variant="danger"` | Destructive actions (Delete, Remove, Rotate API Key) | `` |
| `variant="link"` | Link-style actions with no background or border (View Details, navigation-style CTAs) | `` |
+| `variant="subtle"` | **ActionIcon only.** Transparent background with hover highlight; for toolbar/utility icons that shouldn't draw attention until hovered (collapse toggles, close buttons, auxiliary controls) | `...` |
+
+### Correct Usage
+
+```tsx
+
+
+
+
+...
+...
+...
+...
+...
+```
### DO NOT USE (Forbidden Patterns)
-The following patterns are **NOT ALLOWED** for Button and ActionIcon:
-
```tsx
-// ❌ WRONG - Don't use these
@@ -54,20 +66,12 @@ The following patterns are **NOT ALLOWED** for Button and ActionIcon:
...
...
-
-// ✅ CORRECT - Use custom variants
-
-
-
-
-...
-...
-...
-...
```
**Link variant details**: Renders with no background, no border, and muted text color. On hover, text brightens to full contrast. Use for link-style CTAs that should blend into surrounding content (e.g., "View Details", "View Full Trace").
+**Subtle variant details (ActionIcon only)**: Transparent background with standard text color. On hover, a subtle background highlight appears (`--color-bg-hover`). This is the **default** ActionIcon variant. Use for toolbar icons, collapse toggles, close buttons, and utility controls that should stay unobtrusive but reveal interactivity on hover. Unlike `link`, `subtle` shows a hover background rather than changing text color.
+
**Note**: `variant="filled"` is still valid for **form inputs** (Select, TextInput, etc.), just not for Button/ActionIcon.
### Icon-Only Buttons → ActionIcon
diff --git a/packages/app/src/DBSearchPage.tsx b/packages/app/src/DBSearchPage.tsx
index 0cbd8026..1d2a1439 100644
--- a/packages/app/src/DBSearchPage.tsx
+++ b/packages/app/src/DBSearchPage.tsx
@@ -64,6 +64,7 @@ import {
import { notifications } from '@mantine/notifications';
import {
IconBolt,
+ IconLayoutSidebarLeftExpand,
IconPlayerPlay,
IconPlus,
IconTags,
@@ -171,6 +172,8 @@ const SearchConfigSchema = z.object({
type SearchConfigFromSchema = z.infer;
+const QUERY_KEY_PREFIX = 'search';
+
// Helper function to get the default source id
export function getDefaultSourceId(
sources: { id: string }[] | undefined,
@@ -261,6 +264,46 @@ function SearchSubmitButton({
);
}
+function ExpandFiltersButton({ onExpand }: { onExpand: () => void }) {
+ return (
+
+
+
+
+
+ );
+}
+
+function SearchResultsCountGroup({
+ isFilterSidebarCollapsed,
+ onExpandFilters,
+ histogramTimeChartConfig,
+ enableParallelQueries,
+}: {
+ isFilterSidebarCollapsed: boolean;
+ onExpandFilters: () => void;
+ histogramTimeChartConfig: BuilderChartConfigWithDateRange;
+ enableParallelQueries?: boolean;
+}) {
+ return (
+
+ {isFilterSidebarCollapsed && (
+
+ )}
+
+
+ );
+}
+
function SearchNumRows({
config,
enabled,
@@ -278,7 +321,7 @@ function SearchNumRows({
const numRows = data?.[0]?.rows;
return (
-
+
{isLoading
? 'Scanned Rows ...'
: error || !numRows
@@ -812,6 +855,9 @@ function DBSearchPage() {
}
}, [analysisMode, setIsLive]);
+ const [isFilterSidebarCollapsed, setIsFilterSidebarCollapsed] =
+ useLocalStorage('isFilterSidebarCollapsed', false);
+
const [denoiseResults, _setDenoiseResults] = useQueryState(
'denoise',
parseAsBoolean.withDefault(false),
@@ -1174,8 +1220,6 @@ function DBSearchPage() {
const [newSourceModalOpened, setNewSourceModalOpened] = useState(false);
- const QUERY_KEY_PREFIX = 'search';
-
const isAnyQueryFetching =
useIsFetching({
queryKey: [QUERY_KEY_PREFIX],
@@ -1761,33 +1805,43 @@ function DBSearchPage() {
height: '100%',
}}
>
-
-
-
+ {!isFilterSidebarCollapsed && (
+
+ setIsFilterSidebarCollapsed(true)}
+ {...searchFilters}
+ />
+
+ )}
{analysisMode === 'pattern' &&
histogramTimeChartConfig != null && (
-
-
+
+ setIsFilterSidebarCollapsed(false)
+ }
+ histogramTimeChartConfig={histogramTimeChartConfig}
/>
-
+ setIsFilterSidebarCollapsed(false)
+ }
+ histogramTimeChartConfig={histogramTimeChartConfig}
enableParallelQueries
/>
diff --git a/packages/app/src/components/DBSearchPageFilters.tsx b/packages/app/src/components/DBSearchPageFilters.tsx
index 6f65f917..025a0bdc 100644
--- a/packages/app/src/components/DBSearchPageFilters.tsx
+++ b/packages/app/src/components/DBSearchPageFilters.tsx
@@ -36,6 +36,7 @@ import {
IconChevronRight,
IconChevronUp,
IconFilterOff,
+ IconLayoutSidebarLeftCollapse,
IconMinus,
IconPin,
IconPinFilled,
@@ -875,6 +876,7 @@ const DBSearchPageFiltersComponent = ({
setFilterRange,
onColumnToggle,
displayedColumns,
+ onCollapse,
}: {
analysisMode: 'results' | 'delta' | 'pattern';
setAnalysisMode: (mode: 'results' | 'delta' | 'pattern') => void;
@@ -887,6 +889,7 @@ const DBSearchPageFiltersComponent = ({
setFilterRange: (key: string, range: { min: number; max: number }) => void;
onColumnToggle?: (column: string) => void;
displayedColumns?: string[];
+ onCollapse?: () => void;
} & FilterStateHook) => {
const setFilterValue = useCallback(
(
@@ -1203,9 +1206,23 @@ const DBSearchPageFiltersComponent = ({
}}
>
-
- Analysis Mode
-
+
+
+ Analysis Mode
+
+ {onCollapse && (
+
+
+
+
+
+ )}
+
diff --git a/packages/app/src/components/SearchTotalCountChart.tsx b/packages/app/src/components/SearchTotalCountChart.tsx
index ce2fde71..e9653cda 100644
--- a/packages/app/src/components/SearchTotalCountChart.tsx
+++ b/packages/app/src/components/SearchTotalCountChart.tsx
@@ -103,7 +103,7 @@ export default function SearchTotalCountChart({
);
return (
-
+
{isLoading ? (
··· Results
) : totalCount !== null && !isError ? (