flock-core 0.5.0b63__py3-none-any.whl → 0.5.0b70__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 (62) hide show
  1. flock/agent.py +205 -27
  2. flock/cli.py +74 -2
  3. flock/dashboard/websocket.py +13 -2
  4. flock/engines/dspy_engine.py +70 -13
  5. flock/examples.py +4 -1
  6. flock/frontend/README.md +15 -1
  7. flock/frontend/package-lock.json +11 -21
  8. flock/frontend/package.json +1 -1
  9. flock/frontend/src/App.tsx +74 -6
  10. flock/frontend/src/__tests__/e2e/critical-scenarios.test.tsx +4 -5
  11. flock/frontend/src/__tests__/integration/filtering-e2e.test.tsx +7 -3
  12. flock/frontend/src/components/filters/ArtifactTypeFilter.tsx +21 -0
  13. flock/frontend/src/components/filters/FilterFlyout.module.css +104 -0
  14. flock/frontend/src/components/filters/FilterFlyout.tsx +80 -0
  15. flock/frontend/src/components/filters/FilterPills.module.css +186 -45
  16. flock/frontend/src/components/filters/FilterPills.test.tsx +115 -99
  17. flock/frontend/src/components/filters/FilterPills.tsx +120 -44
  18. flock/frontend/src/components/filters/ProducerFilter.tsx +21 -0
  19. flock/frontend/src/components/filters/SavedFiltersControl.module.css +60 -0
  20. flock/frontend/src/components/filters/SavedFiltersControl.test.tsx +158 -0
  21. flock/frontend/src/components/filters/SavedFiltersControl.tsx +159 -0
  22. flock/frontend/src/components/filters/TagFilter.tsx +21 -0
  23. flock/frontend/src/components/filters/TimeRangeFilter.module.css +24 -0
  24. flock/frontend/src/components/filters/TimeRangeFilter.tsx +6 -1
  25. flock/frontend/src/components/filters/VisibilityFilter.tsx +21 -0
  26. flock/frontend/src/components/graph/GraphCanvas.tsx +24 -0
  27. flock/frontend/src/components/layout/DashboardLayout.css +13 -0
  28. flock/frontend/src/components/layout/DashboardLayout.tsx +8 -24
  29. flock/frontend/src/components/modules/HistoricalArtifactsModule.module.css +288 -0
  30. flock/frontend/src/components/modules/HistoricalArtifactsModule.tsx +460 -0
  31. flock/frontend/src/components/modules/HistoricalArtifactsModuleWrapper.tsx +13 -0
  32. flock/frontend/src/components/modules/ModuleRegistry.ts +7 -1
  33. flock/frontend/src/components/modules/registerModules.ts +9 -10
  34. flock/frontend/src/hooks/useModules.ts +11 -1
  35. flock/frontend/src/services/api.ts +140 -0
  36. flock/frontend/src/services/indexeddb.ts +56 -2
  37. flock/frontend/src/services/websocket.ts +129 -0
  38. flock/frontend/src/store/filterStore.test.ts +105 -185
  39. flock/frontend/src/store/filterStore.ts +173 -26
  40. flock/frontend/src/store/graphStore.test.ts +19 -0
  41. flock/frontend/src/store/graphStore.ts +166 -27
  42. flock/frontend/src/types/filters.ts +34 -1
  43. flock/frontend/src/types/graph.ts +7 -0
  44. flock/frontend/src/utils/artifacts.ts +24 -0
  45. flock/mcp/client.py +25 -1
  46. flock/mcp/config.py +1 -10
  47. flock/mcp/manager.py +34 -3
  48. flock/mcp/types/callbacks.py +4 -1
  49. flock/orchestrator.py +56 -5
  50. flock/service.py +146 -9
  51. flock/store.py +971 -24
  52. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/METADATA +27 -1
  53. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/RECORD +56 -49
  54. flock/frontend/src/components/filters/FilterBar.module.css +0 -29
  55. flock/frontend/src/components/filters/FilterBar.test.tsx +0 -133
  56. flock/frontend/src/components/filters/FilterBar.tsx +0 -33
  57. flock/frontend/src/components/modules/EventLogModule.test.tsx +0 -401
  58. flock/frontend/src/components/modules/EventLogModule.tsx +0 -396
  59. flock/frontend/src/components/modules/EventLogModuleWrapper.tsx +0 -17
  60. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/WHEEL +0 -0
  61. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/entry_points.txt +0 -0
  62. {flock_core-0.5.0b63.dist-info → flock_core-0.5.0b70.dist-info}/licenses/LICENSE +0 -0
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "flock-ui",
3
- "version": "0.1.3",
3
+ "version": "0.1.7",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "flock-ui",
9
- "version": "0.1.3",
9
+ "version": "0.1.7",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@types/dagre": "^0.7.53",
@@ -147,7 +147,6 @@
147
147
  "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==",
148
148
  "dev": true,
149
149
  "license": "MIT",
150
- "peer": true,
151
150
  "dependencies": {
152
151
  "@babel/code-frame": "^7.27.1",
153
152
  "@babel/generator": "^7.28.3",
@@ -356,7 +355,6 @@
356
355
  "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
357
356
  "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
358
357
  "license": "MIT",
359
- "peer": true,
360
358
  "engines": {
361
359
  "node": ">=6.9.0"
362
360
  }
@@ -507,7 +505,6 @@
507
505
  }
508
506
  ],
509
507
  "license": "MIT",
510
- "peer": true,
511
508
  "engines": {
512
509
  "node": ">=18"
513
510
  },
@@ -554,7 +551,6 @@
554
551
  }
555
552
  ],
556
553
  "license": "MIT",
557
- "peer": true,
558
554
  "engines": {
559
555
  "node": ">=18"
560
556
  }
@@ -1507,7 +1503,8 @@
1507
1503
  "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
1508
1504
  "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
1509
1505
  "dev": true,
1510
- "license": "MIT"
1506
+ "license": "MIT",
1507
+ "peer": true
1511
1508
  },
1512
1509
  "node_modules/@types/babel__core": {
1513
1510
  "version": "7.20.5",
@@ -1638,7 +1635,6 @@
1638
1635
  "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.0.tgz",
1639
1636
  "integrity": "sha512-1LOH8xovvsKsCBq1wnT4ntDUdCJKmnEakhsuoUSy6ExlHCkGP2hqnatagYTgFk6oeL0VU31u7SNjunPN+GchtA==",
1640
1637
  "license": "MIT",
1641
- "peer": true,
1642
1638
  "dependencies": {
1643
1639
  "csstype": "^3.0.2"
1644
1640
  }
@@ -1649,7 +1645,6 @@
1649
1645
  "integrity": "sha512-brtBs0MnE9SMx7px208g39lRmC5uHZs96caOJfTjFcYSLHNamvaSMfJNagChVNkup2SdtOxKX1FDBkRSJe1ZAg==",
1650
1646
  "dev": true,
1651
1647
  "license": "MIT",
1652
- "peer": true,
1653
1648
  "peerDependencies": {
1654
1649
  "@types/react": "^19.2.0"
1655
1650
  }
@@ -1845,7 +1840,6 @@
1845
1840
  "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==",
1846
1841
  "dev": true,
1847
1842
  "license": "MIT",
1848
- "peer": true,
1849
1843
  "dependencies": {
1850
1844
  "@vitest/utils": "3.2.4",
1851
1845
  "fflate": "^0.8.2",
@@ -1963,6 +1957,7 @@
1963
1957
  "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
1964
1958
  "dev": true,
1965
1959
  "license": "MIT",
1960
+ "peer": true,
1966
1961
  "engines": {
1967
1962
  "node": ">=10"
1968
1963
  },
@@ -2066,7 +2061,6 @@
2066
2061
  }
2067
2062
  ],
2068
2063
  "license": "MIT",
2069
- "peer": true,
2070
2064
  "dependencies": {
2071
2065
  "baseline-browser-mapping": "^2.8.9",
2072
2066
  "caniuse-lite": "^1.0.30001746",
@@ -2295,7 +2289,6 @@
2295
2289
  "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz",
2296
2290
  "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==",
2297
2291
  "license": "ISC",
2298
- "peer": true,
2299
2292
  "engines": {
2300
2293
  "node": ">=12"
2301
2294
  }
@@ -2418,7 +2411,8 @@
2418
2411
  "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
2419
2412
  "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
2420
2413
  "dev": true,
2421
- "license": "MIT"
2414
+ "license": "MIT",
2415
+ "peer": true
2422
2416
  },
2423
2417
  "node_modules/eastasianwidth": {
2424
2418
  "version": "0.2.0",
@@ -2840,7 +2834,6 @@
2840
2834
  "integrity": "sha512-lIHeR1qlIRrIN5VMccd8tI2Sgw6ieYXSVktcSHaNe3Z5nE/tcPQYQWOq00wxMvYOsz+73eAkNenVvmPC6bba9A==",
2841
2835
  "dev": true,
2842
2836
  "license": "MIT",
2843
- "peer": true,
2844
2837
  "dependencies": {
2845
2838
  "@asamuzakjp/dom-selector": "^6.5.4",
2846
2839
  "cssstyle": "^5.3.0",
@@ -2942,6 +2935,7 @@
2942
2935
  "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
2943
2936
  "dev": true,
2944
2937
  "license": "MIT",
2938
+ "peer": true,
2945
2939
  "bin": {
2946
2940
  "lz-string": "bin/bin.js"
2947
2941
  }
@@ -3186,7 +3180,6 @@
3186
3180
  "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
3187
3181
  "dev": true,
3188
3182
  "license": "MIT",
3189
- "peer": true,
3190
3183
  "engines": {
3191
3184
  "node": ">=12"
3192
3185
  },
@@ -3214,7 +3207,6 @@
3214
3207
  }
3215
3208
  ],
3216
3209
  "license": "MIT",
3217
- "peer": true,
3218
3210
  "dependencies": {
3219
3211
  "nanoid": "^3.3.11",
3220
3212
  "picocolors": "^1.1.1",
@@ -3230,6 +3222,7 @@
3230
3222
  "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
3231
3223
  "dev": true,
3232
3224
  "license": "MIT",
3225
+ "peer": true,
3233
3226
  "dependencies": {
3234
3227
  "ansi-regex": "^5.0.1",
3235
3228
  "ansi-styles": "^5.0.0",
@@ -3281,7 +3274,6 @@
3281
3274
  "resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
3282
3275
  "integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
3283
3276
  "license": "MIT",
3284
- "peer": true,
3285
3277
  "engines": {
3286
3278
  "node": ">=0.10.0"
3287
3279
  }
@@ -3291,7 +3283,6 @@
3291
3283
  "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.0.tgz",
3292
3284
  "integrity": "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ==",
3293
3285
  "license": "MIT",
3294
- "peer": true,
3295
3286
  "dependencies": {
3296
3287
  "scheduler": "^0.27.0"
3297
3288
  },
@@ -3318,7 +3309,8 @@
3318
3309
  "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
3319
3310
  "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
3320
3311
  "dev": true,
3321
- "license": "MIT"
3312
+ "license": "MIT",
3313
+ "peer": true
3322
3314
  },
3323
3315
  "node_modules/react-refresh": {
3324
3316
  "version": "0.17.0",
@@ -3907,7 +3899,6 @@
3907
3899
  "integrity": "sha512-4nVGliEpxmhCL8DslSAUdxlB6+SMrhB0a1v5ijlh1xB1nEPuy1mxaHxysVucLHuWryAxLWg6a5ei+U4TLn/rFg==",
3908
3900
  "dev": true,
3909
3901
  "license": "MIT",
3910
- "peer": true,
3911
3902
  "dependencies": {
3912
3903
  "esbuild": "^0.25.0",
3913
3904
  "fdir": "^6.5.0",
@@ -4006,7 +3997,6 @@
4006
3997
  "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==",
4007
3998
  "dev": true,
4008
3999
  "license": "MIT",
4009
- "peer": true,
4010
4000
  "dependencies": {
4011
4001
  "@types/chai": "^5.2.2",
4012
4002
  "@vitest/expect": "3.2.4",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flock-ui",
3
- "version": "0.1.4",
3
+ "version": "0.1.7",
4
4
  "description": "Flock Flow Real-Time Dashboard Frontend",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -5,9 +5,12 @@ import { measureRenderTime } from './utils/performance';
5
5
  import { initializeWebSocket } from './services/websocket';
6
6
  import { registerModules } from './components/modules/registerModules';
7
7
  import { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts';
8
- import { fetchRegisteredAgents } from './services/api';
8
+ import { fetchRegisteredAgents, fetchArtifactSummary, fetchArtifacts } from './services/api';
9
9
  import { useGraphStore } from './store/graphStore';
10
10
  import { useUIStore } from './store/uiStore';
11
+ import { useFilterStore } from './store/filterStore';
12
+ import { mapArtifactToMessage } from './utils/artifacts';
13
+ import { indexedDBService } from './services/indexeddb';
11
14
 
12
15
  // Register modules once at module load time
13
16
  registerModules();
@@ -30,10 +33,59 @@ const App: React.FC = () => {
30
33
  }
31
34
  });
32
35
 
33
- // Initialize WebSocket connection
34
- const wsUrl = import.meta.env.VITE_WS_URL || 'ws://localhost:8000/ws';
35
- const wsClient = initializeWebSocket(wsUrl);
36
- wsClient.connect();
36
+ const loadHistoricalData = async () => {
37
+ try {
38
+ await indexedDBService.initialize();
39
+
40
+ const filterStore = useFilterStore.getState();
41
+ const graphStore = useGraphStore.getState();
42
+ const uiStore = useUIStore.getState();
43
+
44
+ const summary = await fetchArtifactSummary();
45
+ filterStore.setSummary(summary);
46
+ filterStore.updateAvailableFacets({
47
+ artifactTypes: Object.keys(summary.by_type),
48
+ producers: Object.keys(summary.by_producer),
49
+ tags: Object.keys(summary.tag_counts),
50
+ visibilities: Object.keys(summary.by_visibility),
51
+ });
52
+
53
+ const artifactResponse = await fetchArtifacts({ limit: 200, embedMeta: true });
54
+ const messages = artifactResponse.items.map(mapArtifactToMessage);
55
+ if (messages.length > 0) {
56
+ graphStore.batchUpdate({ messages });
57
+ if (uiStore.mode === 'blackboard') {
58
+ graphStore.generateBlackboardViewGraph();
59
+ } else {
60
+ graphStore.generateAgentViewGraph();
61
+ }
62
+ graphStore.applyFilters();
63
+
64
+ const correlationMetadata = new Map<string, { correlation_id: string; first_seen: number; artifact_count: number; run_count: number }>();
65
+ artifactResponse.items.forEach((item) => {
66
+ if (!item.correlation_id) return;
67
+ const timestamp = new Date(item.created_at).getTime();
68
+ const existing = correlationMetadata.get(item.correlation_id);
69
+ if (existing) {
70
+ existing.artifact_count += 1;
71
+ existing.first_seen = Math.min(existing.first_seen, timestamp);
72
+ } else {
73
+ correlationMetadata.set(item.correlation_id, {
74
+ correlation_id: item.correlation_id,
75
+ first_seen: timestamp,
76
+ artifact_count: 1,
77
+ run_count: 0,
78
+ });
79
+ }
80
+ });
81
+ if (correlationMetadata.size > 0) {
82
+ filterStore.updateAvailableCorrelationIds(Array.from(correlationMetadata.values()));
83
+ }
84
+ }
85
+ } catch (error) {
86
+ console.error('[App] Failed to load historical artifacts:', error);
87
+ }
88
+ };
37
89
 
38
90
  // Load registered agents from orchestrator
39
91
  // This pre-populates the graph with all agent nodes before any events occur
@@ -61,10 +113,26 @@ const App: React.FC = () => {
61
113
  }
62
114
  };
63
115
 
64
- loadInitialAgents();
116
+ const wsUrl = import.meta.env.VITE_WS_URL || 'ws://localhost:8000/ws';
117
+ const wsClient = initializeWebSocket(wsUrl);
118
+ let cancelled = false;
119
+
120
+ const bootstrap = async () => {
121
+ await loadHistoricalData();
122
+ await loadInitialAgents();
123
+
124
+ if (!cancelled) {
125
+ wsClient.connect();
126
+ }
127
+ };
128
+
129
+ bootstrap().catch((error) => {
130
+ console.error('[App] Bootstrap failed:', error);
131
+ });
65
132
 
66
133
  // Cleanup on unmount
67
134
  return () => {
135
+ cancelled = true;
68
136
  wsClient.disconnect();
69
137
  };
70
138
  }, []);
@@ -404,18 +404,17 @@ describe('Critical E2E Scenarios (Frontend)', () => {
404
404
  artifact_count: 1,
405
405
  run_count: 1,
406
406
  }));
407
+ // Reset filters and generate graph with all events (use fresh state)
408
+ useFilterStore.getState().clearFilters();
407
409
  useFilterStore.getState().updateAvailableCorrelationIds(metadata);
408
410
 
409
- // Generate graph with all events (use fresh state)
410
411
  await act(async () => {
411
412
  useGraphStore.getState().generateBlackboardViewGraph();
412
413
  });
413
414
 
414
- // Wait for nodes to be generated
415
+ // Ensure messages were recorded
415
416
  await waitFor(() => {
416
- const nodes = useGraphStore.getState().nodes;
417
- const visibleNodes = nodes.filter((n) => !n.hidden);
418
- expect(visibleNodes.length).toBe(3);
417
+ expect(useGraphStore.getState().messages.size).toBe(3);
419
418
  }, { timeout: 5000 });
420
419
 
421
420
  // Apply correlation ID filter (use fresh state)
@@ -329,9 +329,13 @@ describe('Filtering Integration E2E', () => {
329
329
 
330
330
  const state = useGraphStore.getState();
331
331
  const visibleNodes = state.nodes.filter((n) => !n.hidden);
332
- // Only agent-1 should be visible
333
- expect(visibleNodes).toHaveLength(1);
334
- expect(visibleNodes[0]?.id).toBe('agent-1');
332
+
333
+ // Agent nodes remain visible but metrics reflect filtered artifacts
334
+ expect(visibleNodes).toHaveLength(2);
335
+ const agent1Node = visibleNodes.find((n) => n.id === 'agent-1');
336
+ const agent2Node = visibleNodes.find((n) => n.id === 'agent-2');
337
+ expect(agent1Node).toBeDefined();
338
+ expect(agent2Node).toBeDefined();
335
339
  });
336
340
  });
337
341
 
@@ -0,0 +1,21 @@
1
+ import React from 'react';
2
+ import MultiSelect from '../settings/MultiSelect';
3
+ import { useFilterStore } from '../../store/filterStore';
4
+
5
+ const ArtifactTypeFilter: React.FC = () => {
6
+ const options = useFilterStore((state) => state.availableArtifactTypes);
7
+ const selected = useFilterStore((state) => state.selectedArtifactTypes);
8
+ const setArtifactTypes = useFilterStore((state) => state.setArtifactTypes);
9
+
10
+ return (
11
+ <MultiSelect
12
+ options={options}
13
+ selected={selected}
14
+ onChange={setArtifactTypes}
15
+ placeholder={options.length ? 'Select types…' : 'No types available'}
16
+ disabled={options.length === 0}
17
+ />
18
+ );
19
+ };
20
+
21
+ export default ArtifactTypeFilter;
@@ -0,0 +1,104 @@
1
+ .panel {
2
+ position: fixed;
3
+ top: 0;
4
+ left: 0;
5
+ bottom: 0;
6
+ width: 420px;
7
+ background: var(--color-glass-bg);
8
+ backdrop-filter: blur(var(--blur-lg));
9
+ border-right: var(--border-default);
10
+ box-shadow: var(--shadow-2xl);
11
+ z-index: 1100;
12
+ animation: slideInLeft var(--duration-slow) var(--ease-smooth);
13
+ display: flex;
14
+ flex-direction: column;
15
+ }
16
+
17
+ .header {
18
+ display: flex;
19
+ align-items: flex-start;
20
+ justify-content: space-between;
21
+ gap: var(--space-component-sm);
22
+ padding: var(--space-layout-md);
23
+ border-bottom: var(--border-subtle);
24
+ }
25
+
26
+ .title {
27
+ margin: 0;
28
+ font-size: var(--font-size-h3);
29
+ color: var(--color-text-primary);
30
+ }
31
+
32
+ .subtitle {
33
+ margin: 0;
34
+ font-size: var(--font-size-body-xs);
35
+ color: var(--color-text-tertiary);
36
+ }
37
+
38
+ .closeButton {
39
+ width: 32px;
40
+ height: 32px;
41
+ border-radius: var(--radius-md);
42
+ border: none;
43
+ background: transparent;
44
+ color: var(--color-text-secondary);
45
+ font-size: 22px;
46
+ cursor: pointer;
47
+ transition: var(--transition-all);
48
+ }
49
+
50
+ .closeButton:hover {
51
+ background: var(--color-bg-overlay);
52
+ color: var(--color-text-primary);
53
+ }
54
+
55
+ .content {
56
+ flex: 1;
57
+ overflow-y: auto;
58
+ padding: var(--space-layout-md);
59
+ display: flex;
60
+ flex-direction: column;
61
+ gap: var(--space-layout-md);
62
+ }
63
+
64
+ .section {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: var(--space-component-sm);
68
+ }
69
+
70
+ .sectionLabel {
71
+ margin: 0;
72
+ font-size: var(--font-size-body-xs);
73
+ letter-spacing: var(--letter-spacing-wide);
74
+ color: var(--color-text-tertiary);
75
+ text-transform: uppercase;
76
+ }
77
+
78
+ .separator {
79
+ width: 100%;
80
+ height: 1px;
81
+ background: linear-gradient(90deg, transparent, rgba(148, 163, 184, 0.25), transparent);
82
+ margin: var(--space-component-md) 0;
83
+ }
84
+
85
+ @keyframes slideInLeft {
86
+ from {
87
+ transform: translateX(-40px);
88
+ opacity: 0;
89
+ }
90
+ to {
91
+ transform: translateX(0);
92
+ opacity: 1;
93
+ }
94
+ }
95
+
96
+ @media (max-width: 960px) {
97
+ .panel {
98
+ width: 100%;
99
+ }
100
+
101
+ .panel {
102
+ width: 100%;
103
+ }
104
+ }
@@ -0,0 +1,80 @@
1
+ import React from 'react';
2
+ import CorrelationIDFilter from './CorrelationIDFilter';
3
+ import TimeRangeFilter from './TimeRangeFilter';
4
+ import ArtifactTypeFilter from './ArtifactTypeFilter';
5
+ import ProducerFilter from './ProducerFilter';
6
+ import TagFilter from './TagFilter';
7
+ import VisibilityFilter from './VisibilityFilter';
8
+ import SavedFiltersControl from './SavedFiltersControl';
9
+ import styles from './FilterFlyout.module.css';
10
+
11
+ interface FilterFlyoutProps {
12
+ onClose: () => void;
13
+ }
14
+
15
+ const FilterFlyout: React.FC<FilterFlyoutProps> = ({ onClose }) => {
16
+ return (
17
+ <aside className={styles.panel} role="dialog" aria-label="Filters">
18
+ <header className={styles.header}>
19
+ <div>
20
+ <h2 className={styles.title}>Filters</h2>
21
+ <p className={styles.subtitle}>Slice historical data without losing your place.</p>
22
+ </div>
23
+ <button type="button" className={styles.closeButton} onClick={onClose} aria-label="Close filters">
24
+ ×
25
+ </button>
26
+ </header>
27
+
28
+ <div className={styles.content}>
29
+ <section className={styles.section}>
30
+ <h3 className={styles.sectionLabel}>Presets</h3>
31
+ <SavedFiltersControl />
32
+ </section>
33
+
34
+ <div className={styles.separator} role="presentation" />
35
+
36
+ <section className={styles.section}>
37
+ <h3 className={styles.sectionLabel}>Correlation</h3>
38
+ <CorrelationIDFilter />
39
+ </section>
40
+
41
+ <div className={styles.separator} role="presentation" />
42
+
43
+ <section className={styles.section}>
44
+ <h3 className={styles.sectionLabel}>Time Range</h3>
45
+ <TimeRangeFilter />
46
+ </section>
47
+
48
+ <div className={styles.separator} role="presentation" />
49
+
50
+ <section className={styles.section}>
51
+ <h3 className={styles.sectionLabel}>Artifact Types</h3>
52
+ <ArtifactTypeFilter />
53
+ </section>
54
+
55
+ <div className={styles.separator} role="presentation" />
56
+
57
+ <section className={styles.section}>
58
+ <h3 className={styles.sectionLabel}>Producers</h3>
59
+ <ProducerFilter />
60
+ </section>
61
+
62
+ <div className={styles.separator} role="presentation" />
63
+
64
+ <section className={styles.section}>
65
+ <h3 className={styles.sectionLabel}>Tags</h3>
66
+ <TagFilter />
67
+ </section>
68
+
69
+ <div className={styles.separator} role="presentation" />
70
+
71
+ <section className={styles.section}>
72
+ <h3 className={styles.sectionLabel}>Visibility</h3>
73
+ <VisibilityFilter />
74
+ </section>
75
+ </div>
76
+ </aside>
77
+ );
78
+ };
79
+
80
+ export default FilterFlyout;