flock-core 0.5.0b50__py3-none-any.whl → 0.5.0b52__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of flock-core might be problematic. Click here for more details.

Files changed (117) hide show
  1. flock/dashboard/launcher.py +1 -1
  2. flock/frontend/README.md +678 -0
  3. flock/frontend/docs/DESIGN_SYSTEM.md +1980 -0
  4. flock/frontend/index.html +12 -0
  5. flock/frontend/package-lock.json +4347 -0
  6. flock/frontend/package.json +48 -0
  7. flock/frontend/src/App.tsx +79 -0
  8. flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +587 -0
  9. flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +387 -0
  10. flock/frontend/src/__tests__/integration/graph-rendering.test.tsx +640 -0
  11. flock/frontend/src/__tests__/integration/indexeddb-persistence.test.tsx +699 -0
  12. flock/frontend/src/components/common/BuildInfo.tsx +39 -0
  13. flock/frontend/src/components/common/EmptyState.module.css +115 -0
  14. flock/frontend/src/components/common/EmptyState.tsx +128 -0
  15. flock/frontend/src/components/common/ErrorBoundary.module.css +169 -0
  16. flock/frontend/src/components/common/ErrorBoundary.tsx +118 -0
  17. flock/frontend/src/components/common/KeyboardShortcutsDialog.css +251 -0
  18. flock/frontend/src/components/common/KeyboardShortcutsDialog.tsx +151 -0
  19. flock/frontend/src/components/common/LoadingSpinner.module.css +97 -0
  20. flock/frontend/src/components/common/LoadingSpinner.tsx +29 -0
  21. flock/frontend/src/components/controls/PublishControl.css +547 -0
  22. flock/frontend/src/components/controls/PublishControl.test.tsx +543 -0
  23. flock/frontend/src/components/controls/PublishControl.tsx +432 -0
  24. flock/frontend/src/components/details/DetailWindowContainer.tsx +62 -0
  25. flock/frontend/src/components/details/LiveOutputTab.test.tsx +792 -0
  26. flock/frontend/src/components/details/LiveOutputTab.tsx +220 -0
  27. flock/frontend/src/components/details/MessageHistoryTab.tsx +299 -0
  28. flock/frontend/src/components/details/NodeDetailWindow.test.tsx +501 -0
  29. flock/frontend/src/components/details/NodeDetailWindow.tsx +218 -0
  30. flock/frontend/src/components/details/RunStatusTab.tsx +307 -0
  31. flock/frontend/src/components/details/tabs.test.tsx +1015 -0
  32. flock/frontend/src/components/filters/CorrelationIDFilter.module.css +102 -0
  33. flock/frontend/src/components/filters/CorrelationIDFilter.test.tsx +197 -0
  34. flock/frontend/src/components/filters/CorrelationIDFilter.tsx +121 -0
  35. flock/frontend/src/components/filters/FilterBar.module.css +29 -0
  36. flock/frontend/src/components/filters/FilterBar.test.tsx +133 -0
  37. flock/frontend/src/components/filters/FilterBar.tsx +33 -0
  38. flock/frontend/src/components/filters/FilterPills.module.css +79 -0
  39. flock/frontend/src/components/filters/FilterPills.test.tsx +173 -0
  40. flock/frontend/src/components/filters/FilterPills.tsx +67 -0
  41. flock/frontend/src/components/filters/TimeRangeFilter.module.css +91 -0
  42. flock/frontend/src/components/filters/TimeRangeFilter.test.tsx +154 -0
  43. flock/frontend/src/components/filters/TimeRangeFilter.tsx +105 -0
  44. flock/frontend/src/components/graph/AgentNode.test.tsx +75 -0
  45. flock/frontend/src/components/graph/AgentNode.tsx +322 -0
  46. flock/frontend/src/components/graph/GraphCanvas.tsx +406 -0
  47. flock/frontend/src/components/graph/MessageFlowEdge.tsx +128 -0
  48. flock/frontend/src/components/graph/MessageNode.test.tsx +62 -0
  49. flock/frontend/src/components/graph/MessageNode.tsx +116 -0
  50. flock/frontend/src/components/graph/MiniMap.tsx +47 -0
  51. flock/frontend/src/components/graph/TransformEdge.tsx +123 -0
  52. flock/frontend/src/components/layout/DashboardLayout.css +407 -0
  53. flock/frontend/src/components/layout/DashboardLayout.tsx +300 -0
  54. flock/frontend/src/components/layout/Header.module.css +88 -0
  55. flock/frontend/src/components/layout/Header.tsx +52 -0
  56. flock/frontend/src/components/modules/EventLogModule.test.tsx +401 -0
  57. flock/frontend/src/components/modules/EventLogModule.tsx +396 -0
  58. flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +17 -0
  59. flock/frontend/src/components/modules/ModuleRegistry.test.ts +333 -0
  60. flock/frontend/src/components/modules/ModuleRegistry.ts +85 -0
  61. flock/frontend/src/components/modules/ModuleWindow.tsx +155 -0
  62. flock/frontend/src/components/modules/registerModules.ts +20 -0
  63. flock/frontend/src/components/settings/AdvancedSettings.tsx +175 -0
  64. flock/frontend/src/components/settings/AppearanceSettings.tsx +185 -0
  65. flock/frontend/src/components/settings/GraphSettings.tsx +110 -0
  66. flock/frontend/src/components/settings/SettingsPanel.css +327 -0
  67. flock/frontend/src/components/settings/SettingsPanel.tsx +131 -0
  68. flock/frontend/src/components/settings/ThemeSelector.tsx +298 -0
  69. flock/frontend/src/hooks/useKeyboardShortcuts.ts +148 -0
  70. flock/frontend/src/hooks/useModulePersistence.test.ts +442 -0
  71. flock/frontend/src/hooks/useModulePersistence.ts +154 -0
  72. flock/frontend/src/hooks/useModules.ts +139 -0
  73. flock/frontend/src/hooks/usePersistence.ts +139 -0
  74. flock/frontend/src/main.tsx +13 -0
  75. flock/frontend/src/services/api.ts +213 -0
  76. flock/frontend/src/services/indexeddb.test.ts +793 -0
  77. flock/frontend/src/services/indexeddb.ts +794 -0
  78. flock/frontend/src/services/layout.test.ts +437 -0
  79. flock/frontend/src/services/layout.ts +146 -0
  80. flock/frontend/src/services/themeApplicator.ts +140 -0
  81. flock/frontend/src/services/themeService.ts +77 -0
  82. flock/frontend/src/services/websocket.test.ts +595 -0
  83. flock/frontend/src/services/websocket.ts +685 -0
  84. flock/frontend/src/store/filterStore.test.ts +242 -0
  85. flock/frontend/src/store/filterStore.ts +103 -0
  86. flock/frontend/src/store/graphStore.test.ts +186 -0
  87. flock/frontend/src/store/graphStore.ts +414 -0
  88. flock/frontend/src/store/moduleStore.test.ts +253 -0
  89. flock/frontend/src/store/moduleStore.ts +57 -0
  90. flock/frontend/src/store/settingsStore.ts +188 -0
  91. flock/frontend/src/store/streamStore.ts +68 -0
  92. flock/frontend/src/store/uiStore.test.ts +54 -0
  93. flock/frontend/src/store/uiStore.ts +110 -0
  94. flock/frontend/src/store/wsStore.ts +34 -0
  95. flock/frontend/src/styles/index.css +15 -0
  96. flock/frontend/src/styles/scrollbar.css +47 -0
  97. flock/frontend/src/styles/variables.css +488 -0
  98. flock/frontend/src/test/setup.ts +1 -0
  99. flock/frontend/src/types/filters.ts +14 -0
  100. flock/frontend/src/types/graph.ts +55 -0
  101. flock/frontend/src/types/modules.ts +7 -0
  102. flock/frontend/src/types/theme.ts +55 -0
  103. flock/frontend/src/utils/mockData.ts +85 -0
  104. flock/frontend/src/utils/performance.ts +16 -0
  105. flock/frontend/src/utils/transforms.test.ts +860 -0
  106. flock/frontend/src/utils/transforms.ts +323 -0
  107. flock/frontend/src/vite-env.d.ts +17 -0
  108. flock/frontend/tsconfig.json +27 -0
  109. flock/frontend/tsconfig.node.json +11 -0
  110. flock/frontend/vite.config.ts +25 -0
  111. flock/frontend/vitest.config.ts +11 -0
  112. flock/helper/cli_helper.py +1 -1
  113. {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/METADATA +1 -1
  114. {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/RECORD +117 -7
  115. {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/WHEEL +0 -0
  116. {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/entry_points.txt +0 -0
  117. {flock_core-0.5.0b50.dist-info → flock_core-0.5.0b52.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Theme Selector Component
3
+ *
4
+ * Searchable dropdown for selecting from 300+ terminal themes.
5
+ * Shows color preview swatches and applies themes in real-time.
6
+ */
7
+
8
+ import React, { useState, useEffect } from 'react';
9
+ import { useSettingsStore } from '../../store/settingsStore';
10
+ import { fetchThemeList, fetchTheme, POPULAR_THEMES } from '../../services/themeService';
11
+ import { applyTheme, resetToDefaultTheme } from '../../services/themeApplicator';
12
+ import { TerminalTheme } from '../../types/theme';
13
+
14
+ const ThemeSelector: React.FC = () => {
15
+ const [themes, setThemes] = useState<string[]>([]);
16
+ const [loading, setLoading] = useState(false);
17
+ const [error, setError] = useState<string | null>(null);
18
+ const [searchQuery, setSearchQuery] = useState('');
19
+ const [previewTheme, setPreviewTheme] = useState<TerminalTheme | null>(null);
20
+
21
+ const currentTheme = useSettingsStore((state) => state.appearance.theme);
22
+ const setTheme = useSettingsStore((state) => state.setTheme);
23
+
24
+ // Load theme list on mount
25
+ useEffect(() => {
26
+ loadThemes();
27
+ }, []);
28
+
29
+ const loadThemes = async () => {
30
+ try {
31
+ setLoading(true);
32
+ setError(null);
33
+ const themeList = await fetchThemeList();
34
+ setThemes(themeList);
35
+ } catch (err) {
36
+ setError('Failed to load themes. Backend may not be running.');
37
+ console.error('Theme loading error:', err);
38
+ // Fallback to popular themes if API fails
39
+ setThemes(POPULAR_THEMES);
40
+ } finally {
41
+ setLoading(false);
42
+ }
43
+ };
44
+
45
+ const handleThemeSelect = async (themeName: string) => {
46
+ try {
47
+ setError(null);
48
+ // Handle default theme (doesn't need API fetch)
49
+ if (themeName === 'default') {
50
+ resetToDefaultTheme();
51
+ setTheme('default');
52
+ setPreviewTheme(null);
53
+ return;
54
+ }
55
+ const theme = await fetchTheme(themeName);
56
+ applyTheme(theme);
57
+ setTheme(themeName);
58
+ setPreviewTheme(null);
59
+ } catch (err) {
60
+ setError(`Failed to load theme "${themeName}"`);
61
+ console.error('Theme application error:', err);
62
+ }
63
+ };
64
+
65
+ const handleThemePreview = async (themeName: string) => {
66
+ try {
67
+ // Handle default theme (doesn't need API fetch)
68
+ if (themeName === 'default') {
69
+ resetToDefaultTheme();
70
+ setPreviewTheme(null);
71
+ return;
72
+ }
73
+ const theme = await fetchTheme(themeName);
74
+ setPreviewTheme(theme);
75
+ applyTheme(theme);
76
+ } catch (err) {
77
+ console.error('Theme preview error:', err);
78
+ }
79
+ };
80
+
81
+ const handleResetTheme = () => {
82
+ resetToDefaultTheme();
83
+ setTheme('default');
84
+ setPreviewTheme(null);
85
+ };
86
+
87
+ // Always include "default" theme at the beginning
88
+ const allThemes = ['default', ...themes];
89
+
90
+ const filteredThemes = allThemes.filter((theme) =>
91
+ theme.toLowerCase().includes(searchQuery.toLowerCase())
92
+ );
93
+
94
+ const popularThemes = filteredThemes.filter((theme) => POPULAR_THEMES.includes(theme));
95
+ const defaultTheme = filteredThemes.includes('default') ? ['default'] : [];
96
+ const otherThemes = filteredThemes.filter((theme) => !POPULAR_THEMES.includes(theme) && theme !== 'default');
97
+
98
+ return (
99
+ <div>
100
+ <div className="settings-field">
101
+ <label htmlFor="theme-search" className="settings-label">
102
+ Search Themes
103
+ </label>
104
+ <input
105
+ id="theme-search"
106
+ type="text"
107
+ value={searchQuery}
108
+ onChange={(e) => setSearchQuery(e.target.value)}
109
+ placeholder="Search 300+ themes..."
110
+ className="settings-input"
111
+ disabled={loading}
112
+ />
113
+ </div>
114
+
115
+ {error && (
116
+ <div style={{
117
+ padding: 'var(--space-component-sm)',
118
+ background: 'var(--color-error-bg)',
119
+ color: 'var(--color-error-light)',
120
+ borderRadius: 'var(--radius-md)',
121
+ marginBottom: 'var(--space-component-md)',
122
+ fontSize: 'var(--font-size-caption)',
123
+ }}>
124
+ {error}
125
+ </div>
126
+ )}
127
+
128
+ {loading && (
129
+ <div style={{
130
+ padding: 'var(--space-component-md)',
131
+ textAlign: 'center',
132
+ color: 'var(--color-text-secondary)',
133
+ }}>
134
+ Loading themes...
135
+ </div>
136
+ )}
137
+
138
+ {!loading && (
139
+ <>
140
+ {/* Current Theme */}
141
+ <div className="settings-field">
142
+ <label className="settings-label">Current Theme</label>
143
+ <div style={{
144
+ padding: 'var(--space-component-sm)',
145
+ background: 'var(--color-bg-elevated)',
146
+ borderRadius: 'var(--radius-md)',
147
+ border: 'var(--border-default)',
148
+ display: 'flex',
149
+ alignItems: 'center',
150
+ justifyContent: 'space-between',
151
+ }}>
152
+ <span style={{ color: 'var(--color-text-primary)' }}>
153
+ {currentTheme === 'default' ? 'Default (Flock Flow)' : currentTheme}
154
+ </span>
155
+ {currentTheme !== 'default' && (
156
+ <button
157
+ onClick={handleResetTheme}
158
+ style={{
159
+ padding: '4px 8px',
160
+ background: 'transparent',
161
+ border: 'var(--border-default)',
162
+ borderRadius: 'var(--radius-sm)',
163
+ color: 'var(--color-text-secondary)',
164
+ fontSize: 'var(--font-size-caption)',
165
+ cursor: 'pointer',
166
+ }}
167
+ >
168
+ Reset
169
+ </button>
170
+ )}
171
+ </div>
172
+ </div>
173
+
174
+ {/* Default Theme - Always first */}
175
+ {defaultTheme.length > 0 && (
176
+ <div className="settings-field">
177
+ <label className="settings-label">Built-in Theme</label>
178
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--gap-xs)' }}>
179
+ <button
180
+ onClick={() => handleThemeSelect('default')}
181
+ onMouseEnter={() => handleThemePreview('default')}
182
+ style={{
183
+ padding: 'var(--space-component-sm)',
184
+ background: currentTheme === 'default' ? 'var(--color-primary-500)' : 'var(--color-bg-elevated)',
185
+ border: 'var(--border-default)',
186
+ borderRadius: 'var(--radius-md)',
187
+ color: currentTheme === 'default' ? 'white' : 'var(--color-text-primary)',
188
+ fontSize: 'var(--font-size-body-sm)',
189
+ cursor: 'pointer',
190
+ textAlign: 'left',
191
+ transition: 'var(--transition-all)',
192
+ }}
193
+ >
194
+ Default (Flock Flow)
195
+ </button>
196
+ </div>
197
+ </div>
198
+ )}
199
+
200
+ {/* Popular Themes */}
201
+ {popularThemes.length > 0 && (
202
+ <div className="settings-field">
203
+ <label className="settings-label">Popular Themes</label>
204
+ <div style={{ display: 'flex', flexDirection: 'column', gap: 'var(--gap-xs)' }}>
205
+ {popularThemes.map((theme) => (
206
+ <button
207
+ key={theme}
208
+ onClick={() => handleThemeSelect(theme)}
209
+ onMouseEnter={() => handleThemePreview(theme)}
210
+ style={{
211
+ padding: 'var(--space-component-sm)',
212
+ background: currentTheme === theme ? 'var(--color-primary-500)' : 'var(--color-bg-elevated)',
213
+ border: 'var(--border-default)',
214
+ borderRadius: 'var(--radius-md)',
215
+ color: currentTheme === theme ? 'white' : 'var(--color-text-primary)',
216
+ fontSize: 'var(--font-size-body-sm)',
217
+ cursor: 'pointer',
218
+ textAlign: 'left',
219
+ transition: 'var(--transition-all)',
220
+ }}
221
+ >
222
+ {theme}
223
+ </button>
224
+ ))}
225
+ </div>
226
+ </div>
227
+ )}
228
+
229
+ {/* All Other Themes */}
230
+ {otherThemes.length > 0 && (
231
+ <div className="settings-field">
232
+ <label className="settings-label">
233
+ All Themes ({otherThemes.length})
234
+ </label>
235
+ <div style={{
236
+ maxHeight: '300px',
237
+ overflowY: 'auto',
238
+ display: 'flex',
239
+ flexDirection: 'column',
240
+ gap: 'var(--gap-xs)',
241
+ padding: 'var(--space-component-xs)',
242
+ background: 'var(--color-bg-base)',
243
+ borderRadius: 'var(--radius-md)',
244
+ }}>
245
+ {otherThemes.map((theme) => (
246
+ <button
247
+ key={theme}
248
+ onClick={() => handleThemeSelect(theme)}
249
+ onMouseEnter={() => handleThemePreview(theme)}
250
+ style={{
251
+ padding: 'var(--space-component-sm)',
252
+ background: currentTheme === theme ? 'var(--color-primary-500)' : 'var(--color-bg-elevated)',
253
+ border: 'var(--border-default)',
254
+ borderRadius: 'var(--radius-sm)',
255
+ color: currentTheme === theme ? 'white' : 'var(--color-text-primary)',
256
+ fontSize: 'var(--font-size-body-sm)',
257
+ cursor: 'pointer',
258
+ textAlign: 'left',
259
+ transition: 'var(--transition-all)',
260
+ }}
261
+ >
262
+ {theme}
263
+ </button>
264
+ ))}
265
+ </div>
266
+ </div>
267
+ )}
268
+
269
+ {filteredThemes.length === 0 && !loading && (
270
+ <div style={{
271
+ padding: 'var(--space-component-md)',
272
+ textAlign: 'center',
273
+ color: 'var(--color-text-tertiary)',
274
+ }}>
275
+ No themes found matching "{searchQuery}"
276
+ </div>
277
+ )}
278
+ </>
279
+ )}
280
+
281
+ {previewTheme && (
282
+ <div style={{
283
+ padding: 'var(--space-component-sm)',
284
+ background: 'var(--color-bg-overlay)',
285
+ borderRadius: 'var(--radius-md)',
286
+ marginTop: 'var(--space-component-md)',
287
+ fontSize: 'var(--font-size-caption)',
288
+ color: 'var(--color-text-tertiary)',
289
+ textAlign: 'center',
290
+ }}>
291
+ Previewing: {previewTheme.name} (click to apply)
292
+ </div>
293
+ )}
294
+ </div>
295
+ );
296
+ };
297
+
298
+ export default ThemeSelector;
@@ -0,0 +1,148 @@
1
+ import { useEffect } from 'react';
2
+ import { useUIStore } from '../store/uiStore';
3
+ import { useSettingsStore } from '../store/settingsStore';
4
+
5
+ interface KeyboardShortcutConfig {
6
+ onToggleMode?: () => void;
7
+ onFocusFilter?: () => void;
8
+ onCloseWindows?: () => void;
9
+ onToggleHelp?: () => void;
10
+ onToggleAgentDetails?: () => void;
11
+ }
12
+
13
+ /**
14
+ * Custom hook for managing global keyboard shortcuts
15
+ *
16
+ * Shortcuts:
17
+ * - Ctrl+M (or Cmd+M): Toggle between Agent View and Blackboard View
18
+ * - Ctrl+F (or Cmd+F): Focus the filter input
19
+ * - Ctrl+, (or Cmd+,): Toggle Settings Panel
20
+ * - Ctrl+Shift+F: Toggle Filters Panel
21
+ * - Ctrl+Shift+P: Toggle Publish Panel
22
+ * - Ctrl+Shift+D: Toggle Agent Details
23
+ * - Ctrl+/ (or Cmd+/): Toggle Keyboard Shortcuts Help
24
+ * - Esc: Close all panels and detail windows
25
+ */
26
+ export const useKeyboardShortcuts = (config: KeyboardShortcutConfig = {}) => {
27
+ useEffect(() => {
28
+ const handleKeyDown = (event: KeyboardEvent) => {
29
+ // Check for modifier key (Ctrl on Windows/Linux, Cmd on Mac)
30
+ const modifier = event.ctrlKey || event.metaKey;
31
+
32
+ // Ctrl+, or Cmd+,: Toggle Settings Panel
33
+ if (modifier && event.key === ',') {
34
+ event.preventDefault();
35
+ const showSettings = useSettingsStore.getState().ui.showSettings;
36
+ useSettingsStore.getState().setShowSettings(!showSettings);
37
+ return;
38
+ }
39
+
40
+ // Ctrl+Shift+F: Toggle Filters Panel
41
+ if (modifier && event.shiftKey && event.key === 'F') {
42
+ event.preventDefault();
43
+ const showFilters = useSettingsStore.getState().ui.showFilters;
44
+ useSettingsStore.getState().setShowFilters(!showFilters);
45
+ return;
46
+ }
47
+
48
+ // Ctrl+Shift+P: Toggle Publish/Controls Panel
49
+ if (modifier && event.shiftKey && event.key === 'P') {
50
+ event.preventDefault();
51
+ const showControls = useSettingsStore.getState().ui.showControls;
52
+ useSettingsStore.getState().setShowControls(!showControls);
53
+ return;
54
+ }
55
+
56
+ // Ctrl+Shift+D: Toggle Agent Details
57
+ if (modifier && event.shiftKey && event.key === 'D') {
58
+ event.preventDefault();
59
+ if (config.onToggleAgentDetails) {
60
+ config.onToggleAgentDetails();
61
+ }
62
+ return;
63
+ }
64
+
65
+ // Ctrl+/ or Cmd+/: Toggle Keyboard Shortcuts Help
66
+ if (modifier && event.key === '/') {
67
+ event.preventDefault();
68
+ if (config.onToggleHelp) {
69
+ config.onToggleHelp();
70
+ }
71
+ return;
72
+ }
73
+
74
+ // Ctrl+M or Cmd+M: Toggle mode
75
+ if (modifier && event.key === 'm') {
76
+ event.preventDefault();
77
+ if (config.onToggleMode) {
78
+ config.onToggleMode();
79
+ } else {
80
+ // Default behavior: toggle mode
81
+ const currentMode = useUIStore.getState().mode;
82
+ useUIStore.getState().setMode(currentMode === 'agent' ? 'blackboard' : 'agent');
83
+ }
84
+ return;
85
+ }
86
+
87
+ // Ctrl+F or Cmd+F: Focus filter (only if not in an input already)
88
+ if (modifier && event.key === 'f') {
89
+ const activeElement = document.activeElement;
90
+ const isInInput = activeElement?.tagName === 'INPUT' ||
91
+ activeElement?.tagName === 'TEXTAREA';
92
+
93
+ if (!isInInput) {
94
+ event.preventDefault();
95
+ if (config.onFocusFilter) {
96
+ config.onFocusFilter();
97
+ } else {
98
+ // Default behavior: focus correlation ID filter
99
+ const filterInput = document.querySelector<HTMLInputElement>(
100
+ 'input[placeholder*="correlation"], input[placeholder*="Correlation"]'
101
+ );
102
+ if (filterInput) {
103
+ filterInput.focus();
104
+ filterInput.select();
105
+ }
106
+ }
107
+ }
108
+ return;
109
+ }
110
+
111
+ // Esc: Close windows/dialogs/panels
112
+ if (event.key === 'Escape') {
113
+ if (config.onCloseWindows) {
114
+ config.onCloseWindows();
115
+ } else {
116
+ // Priority: Close panels first, then detail windows
117
+ const { showSettings, showFilters, showControls } = useSettingsStore.getState().ui;
118
+ const detailWindows = useUIStore.getState().detailWindows;
119
+
120
+ if (showSettings) {
121
+ event.preventDefault();
122
+ useSettingsStore.getState().setShowSettings(false);
123
+ } else if (showControls) {
124
+ event.preventDefault();
125
+ useSettingsStore.getState().setShowControls(false);
126
+ } else if (showFilters) {
127
+ event.preventDefault();
128
+ useSettingsStore.getState().setShowFilters(false);
129
+ } else if (detailWindows.size > 0) {
130
+ event.preventDefault();
131
+ // Close all detail windows
132
+ detailWindows.clear();
133
+ useUIStore.setState({ detailWindows: new Map() });
134
+ }
135
+ }
136
+ return;
137
+ }
138
+ };
139
+
140
+ // Add event listener
141
+ window.addEventListener('keydown', handleKeyDown);
142
+
143
+ // Cleanup
144
+ return () => {
145
+ window.removeEventListener('keydown', handleKeyDown);
146
+ };
147
+ }, [config]);
148
+ };