- {isV2Ui ? (
-
+ {isV2Ui && !v2UseLegacySidebarFilter ? (
+
+
+
+
+
+
+ ) : isV2Ui ? (
+
+ }
+ />
+
+
+
+
) : (
{
expect(appearance.opacity).toBe(0.75);
expect(appearance.blur).toBe(6);
expect(appearance.useNativeMacWindowControls).toBe(true);
+ expect(appearance.v2SidebarSearchMode).toBe('command');
+ expect(appearance.v2CommandSearchPersistentFilterEnabled).toBe(false);
+ expect(appearance.v2SidebarPersistedFilter).toBe('');
expect(appearance.showDataTableVerticalBorders).toBe(false);
expect(appearance.dataTableDensity).toBe('comfortable');
expect(appearance.dataTableFontSize).toBeNull();
@@ -124,6 +127,30 @@ describe('store appearance persistence', () => {
expect(appearance.customMonoFontFamily).toBeNull();
});
+ it('persists v2 sidebar search preferences and sanitizes filter text', async () => {
+ const { useStore } = await importStore();
+
+ useStore.getState().setAppearance({
+ v2SidebarSearchMode: 'filter',
+ v2CommandSearchPersistentFilterEnabled: true,
+ v2SidebarPersistedFilter: ` ${'orders'.repeat(40)} `,
+ });
+
+ const persisted = JSON.parse(storage.getItem('lite-db-storage') || '{}');
+ expect(persisted.state.appearance.v2SidebarSearchMode).toBe('filter');
+ expect(persisted.state.appearance.v2CommandSearchPersistentFilterEnabled).toBe(true);
+ expect(persisted.state.appearance.v2SidebarPersistedFilter).toHaveLength(120);
+ expect(persisted.state.appearance.v2SidebarPersistedFilter.startsWith('orders')).toBe(true);
+
+ vi.resetModules();
+ const reloaded = await importStore();
+ const appearance = reloaded.useStore.getState().appearance;
+
+ expect(appearance.v2SidebarSearchMode).toBe('filter');
+ expect(appearance.v2CommandSearchPersistentFilterEnabled).toBe(true);
+ expect(appearance.v2SidebarPersistedFilter).toHaveLength(120);
+ });
+
it('persists tab display appearance settings and sanitizes invalid elements', async () => {
const { useStore } = await importStore();
diff --git a/frontend/src/store.ts b/frontend/src/store.ts
index 34b71f9..4f27669 100644
--- a/frontend/src/store.ts
+++ b/frontend/src/store.ts
@@ -59,6 +59,9 @@ export interface AppearanceSettings extends DataGridDisplaySettings {
opacity: number;
blur: number;
useNativeMacWindowControls: boolean;
+ v2SidebarSearchMode: "command" | "filter";
+ v2CommandSearchPersistentFilterEnabled: boolean;
+ v2SidebarPersistedFilter: string;
customUIFontFamily: string | null;
customMonoFontFamily: string | null;
tabDisplay: TabDisplaySettings;
@@ -70,6 +73,9 @@ export const DEFAULT_APPEARANCE: AppearanceSettings = {
opacity: 1.0,
blur: 0,
useNativeMacWindowControls: false,
+ v2SidebarSearchMode: "command",
+ v2CommandSearchPersistentFilterEnabled: false,
+ v2SidebarPersistedFilter: "",
customUIFontFamily: null,
customMonoFontFamily: null,
tabDisplay: DEFAULT_TAB_DISPLAY_SETTINGS,
@@ -84,6 +90,20 @@ const MAX_FONT_SIZE = 20;
const DEFAULT_STARTUP_FULLSCREEN = false;
const LEGACY_DEFAULT_OPACITY = 0.95;
const OPACITY_EPSILON = 1e-6;
+const MAX_SIDEBAR_PERSISTED_FILTER_LENGTH = 120;
+
+const sanitizeV2SidebarSearchMode = (
+ value: unknown,
+): AppearanceSettings["v2SidebarSearchMode"] => {
+ return value === "filter" ? "filter" : DEFAULT_APPEARANCE.v2SidebarSearchMode;
+};
+
+const sanitizeV2SidebarPersistedFilter = (value: unknown): string => {
+ if (typeof value !== "string") {
+ return DEFAULT_APPEARANCE.v2SidebarPersistedFilter;
+ }
+ return value.trim().slice(0, MAX_SIDEBAR_PERSISTED_FILTER_LENGTH);
+};
const MAX_URI_LENGTH = 4096;
const MAX_HOST_ENTRY_LENGTH = 512;
const MAX_HOST_ENTRIES = 64;
@@ -1637,6 +1657,16 @@ const sanitizeAppearance = (
typeof appearance.useNativeMacWindowControls === "boolean"
? appearance.useNativeMacWindowControls
: DEFAULT_APPEARANCE.useNativeMacWindowControls,
+ v2SidebarSearchMode: sanitizeV2SidebarSearchMode(
+ appearance.v2SidebarSearchMode,
+ ),
+ v2CommandSearchPersistentFilterEnabled:
+ typeof appearance.v2CommandSearchPersistentFilterEnabled === "boolean"
+ ? appearance.v2CommandSearchPersistentFilterEnabled
+ : DEFAULT_APPEARANCE.v2CommandSearchPersistentFilterEnabled,
+ v2SidebarPersistedFilter: sanitizeV2SidebarPersistedFilter(
+ appearance.v2SidebarPersistedFilter,
+ ),
customUIFontFamily: sanitizeFontFamilyInput(appearance.customUIFontFamily),
customMonoFontFamily: sanitizeFontFamilyInput(appearance.customMonoFontFamily),
tabDisplay: sanitizeTabDisplaySettings(appearance.tabDisplay),
diff --git a/frontend/src/v2-theme.css b/frontend/src/v2-theme.css
index bdf4bdf..aab8d91 100644
--- a/frontend/src/v2-theme.css
+++ b/frontend/src/v2-theme.css
@@ -1831,8 +1831,22 @@ body[data-ui-version="v2"] .gn-v2-explorer-search {
border-bottom: none !important;
}
+body[data-ui-version="v2"] .gn-v2-explorer-command-row,
+body[data-ui-version="v2"] .gn-v2-explorer-legacy-filter-row {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ min-width: 0;
+}
+
+body[data-ui-version="v2"] .gn-v2-explorer-legacy-filter-row .ant-input-affix-wrapper {
+ flex: 1 1 auto;
+ min-width: 0;
+}
+
body[data-ui-version="v2"] .gn-v2-explorer-command-trigger {
- width: 100%;
+ flex: 1 1 auto;
+ min-width: 0;
height: 30px;
display: flex;
align-items: center;
@@ -1862,6 +1876,31 @@ body[data-ui-version="v2"] .gn-v2-explorer-command-trigger > span:nth-child(2) {
white-space: nowrap;
}
+body[data-ui-version="v2"] .gn-v2-explorer-filter-action {
+ width: 30px;
+ height: 30px;
+ flex: 0 0 30px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ border: 0.5px solid var(--gn-br-2);
+ border-radius: 7px;
+ background: var(--gn-bg-input);
+ color: var(--gn-fg-4);
+ cursor: pointer;
+}
+
+body[data-ui-version="v2"] .gn-v2-explorer-filter-action:hover:not(:disabled) {
+ border-color: var(--gn-accent);
+ background: var(--gn-bg-hover);
+ color: var(--gn-fg-2);
+}
+
+body[data-ui-version="v2"] .gn-v2-explorer-filter-action:disabled {
+ opacity: 0.42;
+ cursor: not-allowed;
+}
+
body[data-ui-version="v2"] .gn-v2-command-backdrop {
position: fixed;
inset: 0;
@@ -1918,6 +1957,38 @@ body[data-ui-version="v2"] .gn-v2-command-searchbar > .anticon {
color: var(--gn-fg-4);
}
+body[data-ui-version="v2"] .gn-v2-command-filter-switch {
+ height: 30px;
+ flex: 0 0 auto;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+body[data-ui-version="v2"] .gn-v2-command-searchbar .ant-switch {
+ flex: 0 0 auto;
+}
+
+body[data-ui-version="v2"] .gn-v2-command-searchbar .ant-btn {
+ width: 30px !important;
+ height: 30px !important;
+ flex: 0 0 30px;
+ display: inline-flex !important;
+ align-items: center !important;
+ justify-content: center !important;
+ border-radius: 8px !important;
+ color: var(--gn-fg-4) !important;
+}
+
+body[data-ui-version="v2"] .gn-v2-command-searchbar .ant-btn:hover:not(:disabled) {
+ background: var(--gn-bg-hover) !important;
+ color: var(--gn-fg-2) !important;
+}
+
+body[data-ui-version="v2"] .gn-v2-command-searchbar .ant-btn:disabled {
+ color: var(--gn-fg-5) !important;
+}
+
body[data-ui-version="v2"] .gn-v2-command-searchbar > kbd,
body[data-ui-version="v2"] .gn-v2-command-footer kbd,
body[data-ui-version="v2"] .gn-v2-command-row kbd {