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,242 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { useFilterStore } from './filterStore';
3
+
4
+ describe('filterStore', () => {
5
+ beforeEach(() => {
6
+ const store = useFilterStore.getState();
7
+ store.clearFilters();
8
+ store.updateAvailableCorrelationIds([]);
9
+ });
10
+
11
+ describe('Initial State', () => {
12
+ it('should have no correlation ID selected', () => {
13
+ const state = useFilterStore.getState();
14
+ expect(state.correlationId).toBeNull();
15
+ });
16
+
17
+ it('should have default time range of last 10 minutes', () => {
18
+ const state = useFilterStore.getState();
19
+ expect(state.timeRange).toEqual({ preset: 'last10min' });
20
+ });
21
+
22
+ it('should have empty available correlation IDs', () => {
23
+ const state = useFilterStore.getState();
24
+ expect(state.availableCorrelationIds).toEqual([]);
25
+ });
26
+ });
27
+
28
+ describe('setCorrelationId', () => {
29
+ it('should set correlation ID', () => {
30
+ const store = useFilterStore.getState();
31
+ store.setCorrelationId('test-correlation-id');
32
+
33
+ expect(useFilterStore.getState().correlationId).toBe('test-correlation-id');
34
+ });
35
+
36
+ it('should clear correlation ID when set to null', () => {
37
+ const store = useFilterStore.getState();
38
+ store.setCorrelationId('test-id');
39
+ store.setCorrelationId(null);
40
+
41
+ expect(useFilterStore.getState().correlationId).toBeNull();
42
+ });
43
+ });
44
+
45
+ describe('setTimeRange', () => {
46
+ it('should set time range preset', () => {
47
+ const store = useFilterStore.getState();
48
+ store.setTimeRange({ preset: 'last5min' });
49
+
50
+ expect(useFilterStore.getState().timeRange).toEqual({ preset: 'last5min' });
51
+ });
52
+
53
+ it('should set custom time range', () => {
54
+ const store = useFilterStore.getState();
55
+ const customRange = {
56
+ preset: 'custom' as const,
57
+ start: Date.now() - 3600000,
58
+ end: Date.now(),
59
+ };
60
+ store.setTimeRange(customRange);
61
+
62
+ expect(useFilterStore.getState().timeRange).toEqual(customRange);
63
+ });
64
+ });
65
+
66
+ describe('clearFilters', () => {
67
+ it('should reset all filters to defaults', () => {
68
+ const store = useFilterStore.getState();
69
+ store.setCorrelationId('test-id');
70
+ store.setTimeRange({ preset: 'last1hour' });
71
+
72
+ store.clearFilters();
73
+
74
+ const state = useFilterStore.getState();
75
+ expect(state.correlationId).toBeNull();
76
+ expect(state.timeRange).toEqual({ preset: 'last10min' });
77
+ });
78
+ });
79
+
80
+ describe('updateAvailableCorrelationIds', () => {
81
+ it('should update available correlation IDs with metadata', () => {
82
+ const store = useFilterStore.getState();
83
+ const now = Date.now();
84
+ const metadata = [
85
+ {
86
+ correlation_id: 'abc123',
87
+ first_seen: now - 120000,
88
+ artifact_count: 5,
89
+ run_count: 2,
90
+ },
91
+ {
92
+ correlation_id: 'def456',
93
+ first_seen: now - 60000,
94
+ artifact_count: 3,
95
+ run_count: 1,
96
+ },
97
+ ];
98
+
99
+ store.updateAvailableCorrelationIds(metadata);
100
+
101
+ const state = useFilterStore.getState();
102
+ // Should be sorted by most recent first
103
+ expect(state.availableCorrelationIds).toHaveLength(2);
104
+ expect(state.availableCorrelationIds[0]?.correlation_id).toBe('def456');
105
+ expect(state.availableCorrelationIds[1]?.correlation_id).toBe('abc123');
106
+ });
107
+
108
+ it('should limit to 50 correlation IDs', () => {
109
+ const store = useFilterStore.getState();
110
+ const metadata = Array.from({ length: 100 }, (_, i) => ({
111
+ correlation_id: `id-${i}`,
112
+ first_seen: Date.now() - i * 1000,
113
+ artifact_count: i,
114
+ run_count: 1,
115
+ }));
116
+
117
+ store.updateAvailableCorrelationIds(metadata);
118
+
119
+ const state = useFilterStore.getState();
120
+ expect(state.availableCorrelationIds).toHaveLength(50);
121
+ });
122
+
123
+ it('should sort by most recent first', () => {
124
+ const store = useFilterStore.getState();
125
+ const now = Date.now();
126
+ const metadata = [
127
+ {
128
+ correlation_id: 'oldest',
129
+ first_seen: now - 300000,
130
+ artifact_count: 1,
131
+ run_count: 1,
132
+ },
133
+ {
134
+ correlation_id: 'newest',
135
+ first_seen: now - 10000,
136
+ artifact_count: 2,
137
+ run_count: 1,
138
+ },
139
+ {
140
+ correlation_id: 'middle',
141
+ first_seen: now - 120000,
142
+ artifact_count: 3,
143
+ run_count: 1,
144
+ },
145
+ ];
146
+
147
+ store.updateAvailableCorrelationIds(metadata);
148
+
149
+ const state = useFilterStore.getState();
150
+ expect(state.availableCorrelationIds[0]?.correlation_id).toBe('newest');
151
+ expect(state.availableCorrelationIds[1]?.correlation_id).toBe('middle');
152
+ expect(state.availableCorrelationIds[2]?.correlation_id).toBe('oldest');
153
+ });
154
+ });
155
+
156
+ describe('getActiveFilters', () => {
157
+ it('should return empty array when no filters active', () => {
158
+ const store = useFilterStore.getState();
159
+ const activeFilters = store.getActiveFilters();
160
+ expect(activeFilters).toEqual([]);
161
+ });
162
+
163
+ it('should return correlation ID filter when set', () => {
164
+ const store = useFilterStore.getState();
165
+ store.setCorrelationId('test-123');
166
+
167
+ const activeFilters = store.getActiveFilters();
168
+ expect(activeFilters).toHaveLength(1);
169
+ expect(activeFilters[0]).toEqual({
170
+ type: 'correlationId',
171
+ value: 'test-123',
172
+ label: 'Correlation ID: test-123',
173
+ });
174
+ });
175
+
176
+ it('should return time range filter when not default', () => {
177
+ const store = useFilterStore.getState();
178
+ store.setTimeRange({ preset: 'last5min' });
179
+
180
+ const activeFilters = store.getActiveFilters();
181
+ expect(activeFilters).toHaveLength(1);
182
+ expect(activeFilters[0]).toEqual({
183
+ type: 'timeRange',
184
+ value: { preset: 'last5min' },
185
+ label: 'Time: Last 5 min',
186
+ });
187
+ });
188
+
189
+ it('should return custom time range filter with formatted dates', () => {
190
+ const store = useFilterStore.getState();
191
+ const start = Date.now() - 3600000;
192
+ const end = Date.now();
193
+ store.setTimeRange({ preset: 'custom', start, end });
194
+
195
+ const activeFilters = store.getActiveFilters();
196
+ expect(activeFilters).toHaveLength(1);
197
+ expect(activeFilters[0]?.type).toBe('timeRange');
198
+ expect(activeFilters[0]?.label).toMatch(/^Time: /);
199
+ });
200
+
201
+ it('should return both filters when both are active', () => {
202
+ const store = useFilterStore.getState();
203
+ store.setCorrelationId('test-123');
204
+ store.setTimeRange({ preset: 'last1hour' });
205
+
206
+ const activeFilters = store.getActiveFilters();
207
+ expect(activeFilters).toHaveLength(2);
208
+ });
209
+ });
210
+
211
+ describe('removeFilter', () => {
212
+ it('should remove correlation ID filter', () => {
213
+ const store = useFilterStore.getState();
214
+ store.setCorrelationId('test-123');
215
+
216
+ store.removeFilter('correlationId');
217
+
218
+ expect(useFilterStore.getState().correlationId).toBeNull();
219
+ });
220
+
221
+ it('should reset time range filter to default', () => {
222
+ const store = useFilterStore.getState();
223
+ store.setTimeRange({ preset: 'last1hour' });
224
+
225
+ store.removeFilter('timeRange');
226
+
227
+ expect(useFilterStore.getState().timeRange).toEqual({ preset: 'last10min' });
228
+ });
229
+
230
+ it('should not affect other filters', () => {
231
+ const store = useFilterStore.getState();
232
+ store.setCorrelationId('test-123');
233
+ store.setTimeRange({ preset: 'last1hour' });
234
+
235
+ store.removeFilter('correlationId');
236
+
237
+ const state = useFilterStore.getState();
238
+ expect(state.correlationId).toBeNull();
239
+ expect(state.timeRange).toEqual({ preset: 'last1hour' });
240
+ });
241
+ });
242
+ });
@@ -0,0 +1,103 @@
1
+ import { create } from 'zustand';
2
+ import { devtools } from 'zustand/middleware';
3
+ import { TimeRange, CorrelationIdMetadata } from '../types/filters';
4
+
5
+ export interface ActiveFilter {
6
+ type: 'correlationId' | 'timeRange';
7
+ value: string | TimeRange;
8
+ label: string;
9
+ }
10
+
11
+ interface FilterState {
12
+ // Active filters
13
+ correlationId: string | null;
14
+ timeRange: TimeRange;
15
+
16
+ // Autocomplete data
17
+ availableCorrelationIds: CorrelationIdMetadata[];
18
+
19
+ // Actions
20
+ setCorrelationId: (id: string | null) => void;
21
+ setTimeRange: (range: TimeRange) => void;
22
+ clearFilters: () => void;
23
+
24
+ // Update available IDs
25
+ updateAvailableCorrelationIds: (metadata: CorrelationIdMetadata[]) => void;
26
+
27
+ // Get active filters
28
+ getActiveFilters: () => ActiveFilter[];
29
+
30
+ // Remove specific filter
31
+ removeFilter: (type: 'correlationId' | 'timeRange') => void;
32
+ }
33
+
34
+ const formatTimeRange = (range: TimeRange): string => {
35
+ if (range.preset === 'last5min') return 'Last 5 min';
36
+ if (range.preset === 'last10min') return 'Last 10 min';
37
+ if (range.preset === 'last1hour') return 'Last hour';
38
+ if (range.preset === 'custom' && range.start && range.end) {
39
+ const startDate = new Date(range.start).toLocaleString();
40
+ const endDate = new Date(range.end).toLocaleString();
41
+ return `${startDate} - ${endDate}`;
42
+ }
43
+ return 'Unknown';
44
+ };
45
+
46
+ export const useFilterStore = create<FilterState>()(
47
+ devtools(
48
+ (set, get) => ({
49
+ correlationId: null,
50
+ timeRange: { preset: 'last10min' },
51
+ availableCorrelationIds: [],
52
+
53
+ setCorrelationId: (id) => set({ correlationId: id }),
54
+ setTimeRange: (range) => set({ timeRange: range }),
55
+ clearFilters: () =>
56
+ set({
57
+ correlationId: null,
58
+ timeRange: { preset: 'last10min' },
59
+ }),
60
+
61
+ updateAvailableCorrelationIds: (metadata) => {
62
+ // Sort by most recent first
63
+ const sorted = [...metadata].sort((a, b) => b.first_seen - a.first_seen);
64
+ set({
65
+ availableCorrelationIds: sorted.slice(0, 50),
66
+ });
67
+ },
68
+
69
+ getActiveFilters: () => {
70
+ const state = get();
71
+ const filters: ActiveFilter[] = [];
72
+
73
+ if (state.correlationId) {
74
+ filters.push({
75
+ type: 'correlationId',
76
+ value: state.correlationId,
77
+ label: `Correlation ID: ${state.correlationId}`,
78
+ });
79
+ }
80
+
81
+ // Only add time range if it's not the default
82
+ if (state.timeRange.preset !== 'last10min') {
83
+ filters.push({
84
+ type: 'timeRange',
85
+ value: state.timeRange,
86
+ label: `Time: ${formatTimeRange(state.timeRange)}`,
87
+ });
88
+ }
89
+
90
+ return filters;
91
+ },
92
+
93
+ removeFilter: (type) => {
94
+ if (type === 'correlationId') {
95
+ set({ correlationId: null });
96
+ } else if (type === 'timeRange') {
97
+ set({ timeRange: { preset: 'last10min' } });
98
+ }
99
+ },
100
+ }),
101
+ { name: 'filterStore' }
102
+ )
103
+ );
@@ -0,0 +1,186 @@
1
+ import { describe, it, expect, beforeEach } from 'vitest';
2
+ import { useGraphStore } from './graphStore';
3
+ import { Agent, Message } from '../types/graph';
4
+
5
+ describe('graphStore', () => {
6
+ beforeEach(() => {
7
+ // Reset store before each test
8
+ useGraphStore.setState({
9
+ agents: new Map(),
10
+ messages: new Map(),
11
+ events: [],
12
+ nodes: [],
13
+ edges: [],
14
+ });
15
+ });
16
+
17
+ it('should add an agent', () => {
18
+ const agent: Agent = {
19
+ id: 'test-agent',
20
+ name: 'test-agent',
21
+ status: 'idle',
22
+ subscriptions: ['Movie'],
23
+ lastActive: Date.now(),
24
+ sentCount: 0,
25
+ recvCount: 0,
26
+ };
27
+
28
+ useGraphStore.getState().addAgent(agent);
29
+
30
+ const agents = useGraphStore.getState().agents;
31
+ expect(agents.size).toBe(1);
32
+ expect(agents.get('test-agent')).toEqual(agent);
33
+ });
34
+
35
+ it('should update an agent', () => {
36
+ const agent: Agent = {
37
+ id: 'test-agent',
38
+ name: 'test-agent',
39
+ status: 'idle',
40
+ subscriptions: [],
41
+ lastActive: Date.now(),
42
+ sentCount: 0,
43
+ recvCount: 0,
44
+ };
45
+
46
+ useGraphStore.getState().addAgent(agent);
47
+ useGraphStore.getState().updateAgent('test-agent', { status: 'running', sentCount: 5 });
48
+
49
+ const updatedAgent = useGraphStore.getState().agents.get('test-agent');
50
+ expect(updatedAgent?.status).toBe('running');
51
+ expect(updatedAgent?.sentCount).toBe(5);
52
+ });
53
+
54
+ it('should add a message', () => {
55
+ const message: Message = {
56
+ id: 'msg-1',
57
+ type: 'Movie',
58
+ payload: { title: 'Test Movie' },
59
+ timestamp: Date.now(),
60
+ correlationId: 'corr-1',
61
+ producedBy: 'movie',
62
+ };
63
+
64
+ useGraphStore.getState().addMessage(message);
65
+
66
+ const messages = useGraphStore.getState().messages;
67
+ const events = useGraphStore.getState().events;
68
+ expect(messages.size).toBe(1);
69
+ expect(messages.get('msg-1')).toEqual(message);
70
+ expect(events.length).toBe(1);
71
+ expect(events[0]).toEqual(message);
72
+ });
73
+
74
+ it('should limit events to 100', () => {
75
+ for (let i = 0; i < 120; i++) {
76
+ const message: Message = {
77
+ id: `msg-${i}`,
78
+ type: 'Movie',
79
+ payload: { index: i },
80
+ timestamp: Date.now(),
81
+ correlationId: 'corr-1',
82
+ producedBy: 'movie',
83
+ };
84
+ useGraphStore.getState().addMessage(message);
85
+ }
86
+
87
+ const events = useGraphStore.getState().events;
88
+ expect(events.length).toBe(100);
89
+ });
90
+
91
+ it('should batch update agents and messages', () => {
92
+ const agents: Agent[] = [
93
+ {
94
+ id: 'agent-1',
95
+ name: 'agent-1',
96
+ status: 'idle',
97
+ subscriptions: [],
98
+ lastActive: Date.now(),
99
+ sentCount: 0,
100
+ recvCount: 0,
101
+ },
102
+ {
103
+ id: 'agent-2',
104
+ name: 'agent-2',
105
+ status: 'running',
106
+ subscriptions: [],
107
+ lastActive: Date.now(),
108
+ sentCount: 0,
109
+ recvCount: 0,
110
+ },
111
+ ];
112
+
113
+ const messages: Message[] = [
114
+ {
115
+ id: 'msg-1',
116
+ type: 'Movie',
117
+ payload: {},
118
+ timestamp: Date.now(),
119
+ correlationId: 'corr-1',
120
+ producedBy: 'agent-1',
121
+ },
122
+ ];
123
+
124
+ useGraphStore.getState().batchUpdate({ agents, messages });
125
+
126
+ expect(useGraphStore.getState().agents.size).toBe(2);
127
+ expect(useGraphStore.getState().messages.size).toBe(1);
128
+ expect(useGraphStore.getState().events.length).toBe(1);
129
+ });
130
+
131
+ it('should generate agent view graph', () => {
132
+ const agents: Agent[] = [
133
+ {
134
+ id: 'movie',
135
+ name: 'movie',
136
+ status: 'idle',
137
+ subscriptions: [],
138
+ lastActive: Date.now(),
139
+ sentCount: 2,
140
+ recvCount: 0,
141
+ position: { x: 0, y: 0 },
142
+ },
143
+ {
144
+ id: 'tagline',
145
+ name: 'tagline',
146
+ status: 'idle',
147
+ subscriptions: ['Movie'],
148
+ lastActive: Date.now(),
149
+ sentCount: 0,
150
+ recvCount: 2,
151
+ position: { x: 200, y: 0 },
152
+ },
153
+ ];
154
+
155
+ const messages: Message[] = [
156
+ {
157
+ id: 'msg-1',
158
+ type: 'Movie',
159
+ payload: {},
160
+ timestamp: Date.now(),
161
+ correlationId: 'corr-1',
162
+ producedBy: 'movie',
163
+ },
164
+ {
165
+ id: 'msg-2',
166
+ type: 'Movie',
167
+ payload: {},
168
+ timestamp: Date.now(),
169
+ correlationId: 'corr-1',
170
+ producedBy: 'movie',
171
+ },
172
+ ];
173
+
174
+ useGraphStore.getState().batchUpdate({ agents, messages });
175
+ // Phase 11 fix: Record consumption to populate consumed_by field
176
+ useGraphStore.getState().recordConsumption(['msg-1', 'msg-2'], 'tagline');
177
+ useGraphStore.getState().generateAgentViewGraph();
178
+
179
+ const nodes = useGraphStore.getState().nodes;
180
+ const edges = useGraphStore.getState().edges;
181
+
182
+ expect(nodes.length).toBe(2);
183
+ expect(nodes[0]?.type).toBe('agent');
184
+ expect(edges.length).toBeGreaterThan(0);
185
+ });
186
+ });